entry-header-eye-catch.html
entry-title-container.html

entry-header-author-info.html
Article by

PHPカンファレンス仙台2019で登壇しました

こんにちはこんにちは! pixiv運営本部エンジニアのうさみ(@tadsan)です

さる2019年1月26日に宮城県仙台市でPHPカンファレンス仙台2019が開催されました。私うさみがセッション発表したほか、スポンサーの一社として協賛させていただきました。

発表の背景

私は以前より公私にわたってPHPの静的解析に取り組んでおり、2016年に書いたPhan静的解析がもたらす大PHP型検査時代 - pixiv insideは多くのPHP技術者のみなさまに読んでいただきました。

その後もWEB+DB PRESS総集編 [Vol.1~102]収載のPHPDoc解説記事2018年のPHPDoc事情とPSR-5など、継続的にPSR-5について追跡しています。

私個人の推しPSRとしてはPSR-5 PHPDocを追っているのですが、そのほかの各PSRについても一定の理解を得たいという思いのもと、今回の発表テーマを選定しました。

参加の感想と技術的な補足

感想については個人のMediumに、スライドの技術的な補足についてはPSRについて話したPHPカンファレンス仙台2019 #phpconsen - 超PHPerになろうに、大雑把に書きました。

また、今回のテーマであるPSRとはそもそも何なのかということについてPSRの誤解 - Qiitaに書きました。

PSR (PHP Standards Recommendations、PHP標準勧告)は権威的な名前ですが、PHPを使って構築されたフレームワークやCMSなどの開発者間での合意に過ぎません。PSRを勧告される対象は、PHP-FIGのメンバーだけのはずです。

われわれピクシブもPHP-FIGのメンバーではありませんが、その成果物を勝手に利用しているという立場です。

pixivとPSRについて

さて、今回の発表テーマは個人的な技術的興味のもとに選んだものですが、内容のいくつかについては業務時間中に調べた経験によるものです。その上で、ピクシブ社内で使っているPSRは、コーディング規約としてのPSR-1とPSR-2、実装としてのPSR-7だけです。

PSR-1について

原則としてはPSR-1に従っています。(ただし、古くからある一部ファイルでは他のファイルをincludeするなど、違反しているものがあります)

勧告の要点は以下の通りです。

  • PHPコードは「<?php」及び 「<?=」タグを使用しなければなりません。
  • 文字コードはUTF-8(BOM無し)を使用しなければなりません。 *シンボル(クラス、関数、定数など)を宣言するためのファイルと、副作用のある処理(出力の生成、ini設定の変更など)を行うためのファイルは、分けるべきです。
  • 名前空間、クラスについては「オートローディング」PSR(PSR-0, PSR-4)に準拠しなければなりません。
  • クラス名は、StudlyCaps(単語の先頭文字を大文字で表記する記法)記法で定義しなければなりません。
  • クラス定数は全て大文字とし、区切り文字にはアンダースコアを用いて定義しなければなりません。
  • メソッド名はcamelCase記法で定義しなければなりません。

PSR-1 基本コーディング規約(日本語)|北海道札幌市のシステム開発会社インフィニットループより引用し、オートローディングに関する記述を一部改変)

幸いにして2007年に開始されたpixivはリリース当初からDBもソースコードもUTF-8だったので、エンコーディングの不整合に起因する問題はほとんどありませんでした。

クラス名についてPSR-4ではPHPの言語機能のnamespaceを利用することになっていますが、pixiv.gitでは基本的に _ 区切りの擬似名前空間を維持しています。これにはいくつかの理由があります。現在こそ、PhanやPHPStanなどの静的ツールを使った継続的インテグレーションで迅速にエラー検出することは容易ですが、ほんの二年ほど前までpixivではそのような体制になっていなかったため、useを使ったクラス名の別名参照や相対参照はリファクタリングの大きな妨げとなっていました。

またPSR-0およびPSR-4ではトップレベルに「ベンダー名」を付与することを要求していますがpixivのコア機能を提供する共通APIでは冗長なのでベンダー名を持っていません。

composer.json では意図的に以下のような記述をしていますが Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance と警告されることには気をつけてください。(本番サービスにリリースする際には composer install のオプションに --optimize-autoloader --apcu-autoloader を付与してクラスマップを生成しているため、パフォーマンス上のペナルティはありません

    "autoload": {
        "psr-0": {
            "": "pixiv-lib"
        }
    },

PSR-2について

PSR-2はPSR-1よりもさらに踏み込んだコーディング規約のガイドラインです。

  • PSR-1に準拠しなければなりません。
  • インデントには4つのスペースを使用し、タブは使用してはいけません。
  • 行の長さに対してハードリミットがあってはいけません。ソフトリミットは120文字を上限とし、実際は80文字以内に抑えるべきです。
  • 名前空間定義のあとには空行を挟まなければいけません。またuse定義ブロックのあとにも同様に空行を挟まなければなりません。
  • クラスの開き括弧は次の行に記述しなければなりません。また閉じ括弧は本文最後の次の行に記述しなければなりません。
  • メソッドの開き括弧は次の行に記述しなければなりません。また閉じ括弧は本文最後の次の行に記述しなければなりません。
  • アクセス修飾子は、全てのプロパティ、メソッドに定義しなければなりません。またabstractとfinalはアクセス修飾子の前に定義し、staticはアクセス修飾子の後に定義しなければなりません。
  • 制御構造の開始時は、その後に1スペースを開けなければなりません。メソッドや関数の呼び出しはスペースを開けてはいけません。
  • 制御構造の開き括弧は同じ行に記述しなければなりません。また閉じ括弧は本文最後の次の行に記述しなければなりません。
  • 制御構造の開始前にスペースがあってはいけません。また閉じる際もその前にスペースがあってはいけません。

PSR-2 コーディングガイド(日本語)|北海道札幌市のシステム開発会社インフィニットループより引用)

pixivでは基本的にPSR-2に則りつつも、一部項目を改変してコーディング標準としています。

  • プロパティ名・メソット名およびクラス定数名に、protectedまたはprivateを示すためにシングルアンダースコアを使用します

これを採用する理由は、前述の通り静的解析の困難さによります。PhpStormやPHPStanを使えばコーディング時にプロパティやメソッドの可視性を把握することは容易ですが、厳密な静的解析によらない文脈でのリファクタリング性を少しでも高めたいためです。

PSR-3について

ロギングのための基盤としては使ってません。独自のロガークラスを用意して、テキストファイルに書き出したものをFluentdで集約しています。

ただしデバッグ用途のChrome Logger - Server side application debuggingの実装としてMonolog/ChromePHPHandlerを使っています。

PSR-7について

pixivからは外部サービスからのデータ取得、社内の他サービスの同期的な強調などにHTTPリクエストを利用する場面があります。そのような用途にはPHPから、もっぱらHTTPクライアントライブラリのGuzzleから利用します。

Guzzleを採用する前の問題点として、コードのさまざまな箇所からcurl系関数やfile_get_contents()を使って外部リソースへのHTTPリクエストが実装していました。このような実装は統一感がないだけではなく、ユニットテスト時に実際のHTTPリクエストが行なわれないようにするために個々の実装が必要になるなど野放図になりがちでした。

Guzzleを導入することによる実用的なメリットは多々ありますが、テスト時にHTTPリクエストをする各箇所で $client = new \GuzzleHttp\Client() のように直接インスタンスを生成するのではなく、生成手続きを別のクラスにまとめたことでテスト時のモックが容易になりました。実装については、また別の機会にどこかで紹介できればと思います。

……そうそう、PSR-7の話でしたね。テスト時は必ず明示的に HandlerStack を設定することで、外部へのリクエストを抑制します。また、 HandlerStackMockHandler をセットすることで、APIから任意のレスポンスが帰ってきた場合のテストを書くことができます。で、そこで GuzzleHttp\Psr7\Response オブジェクトを使ってるということでした。

結構Guzzleの実装に寄ってるので、あまりPSRに依存してる感じがしないですね。ここの実装依存をなくすにはどうすればよいか、という話についてはPHPカンファレンス2018で田中ひさてるさんが発表したPHP-FIGのHTTP処理標準の設計はなぜPSR-7/15/17になったのか - Speaker Deckを読んでいただくと、とても参考になると思います。

そのほかのPHPカンファレンスについて

来たる3月29日から31日までの三日間開催されるPHPerKaigi 2019では、私うさみもセッション発表、運営コアスタッフとして参加します。ここではPHPのCI環境(静的解析・テストなど)の概要について共有いたします。チケットはまだ販売中ですので、奮ってご参加ください。

20191219022007
tadsan
1989年生まれ。北海道砂川市出身。北海道工業大学を留年した後、札幌のSSL証明書リセラーでのアルバイトを経て2012年にピクシブ入社。pixiv運営本部 技術基盤チームリーダー、技術マネジメント室所属。WEB+DB PRESS連載、PHPカンファレンス登壇などでPHP開発の知見の普及に努める。2017年よりEmacs php-modeのリードメンテナーを引き継ぐ。