先日のPHPerKaigi 2018はPHPに興味がある多くのひとびとと直接コミュニケーションがとれる貴重な機会でした1。その中でPSRシリーズへの誤解が聞かれたので一度整理します。
最初にまとめ
- 「PSRはモダン、準拠しないのはレガシー野郎」といったものではない
- 相互運用させることを想定しないのなら、100%準拠することに利点はない
- PSR-2は、それ自身を厳守させるためのコーディングスタイルではない
- PSRは参考するには値するが、自分たちの事情に合ったものを取り込むべし
後述しますが、筆者の所属するチームではPSR-1, PSR-2を参考にしつつ破って使ってます。
PSRは誰が作ってるの?
PSRを管理するのはPHP-FIG (The PHP Framework Interoperability Group、PHPフレームワーク相互運用グループ)です。この組織はPHPのフレームワーク・CMS・ツールなどの開発者の寄合所帯です。
Frequently Asked Questions - PHP-FIGの内容をざっくり訳します。
PHP-FIGの目的は何ですか?
グループの背景にある考えは、代表者たちがプロジェクト間の共通点について話し合うことで協働する方法を探ることです。グループの議論の主な観客は参加プロジェクト同士ですが、そのほかのPHPコミュニティも注目していることはよく承知しています。ほかの人々がPSRの成果物を採用することは歓迎しますが、それは目的ではありません。あなたがどうやってアプリケーションを構築すればいいのかを教えてくれるひとは居ません。
PSRって要は何なの?
PSR (PHP Standards Recommendations、PHP標準勧告)は権威的な名前ですが、PHPを使って構築されたフレームワークやCMSなどの開発者間での合意に過ぎません。PSRを勧告される対象は、PHP-FIGのメンバーだけのはずです。
その策定プロセスはPHP本体(The PHP Group)とも関係なく、「PHP開発者全員が知っておかなければモダンなPHPを書けない」といった性質のものではありません。
先ほどと同じくFrequently Asked Questions - PHP-FIGから抜萃してざっくり訳します。
投票メンバーは標準を遵守しなければいけませんか?
いいえ。PHP-FIGの投票メンバーになることで、すべての、あるいは任意のPSRを実装することを強制されることはありません。プロジェクトはしかるべき時にアップグレードする際に後方互換性を考慮する必要があります。ほとんどのプロジェクトで最終的に採用されると想定されますが、必須ではありません。
PSRでは何が定義されるの?
ざっくり分けて、「オートローディング規約」「コーディングスタイル」と「インターフェイス」です。
オートローディング規約
PHPはspl_autoload_register()
にクラスローダー関数を登録することで、クラスを自動で遅延ロードさせることができます。これについてはincludeって書きたくない僕たちのためのオートローディングとComposerに書きました。遅延ロードとは、ざっくり言ってクラスを「必要になったときに読み込む」ことです。
これはPSR-4と、廃止済みのPSR-0が該当します。これらの規約は、特定のディレクトリ内のファイルと名前空間・クラス名を紐付けて、クラスの自動ロードに適応させるための規則です。PSR-4: Autoloader - PHP-FIGは至って短い文章ですが、相互運用性のためにはとても重要です。
……そう、相互運用性のために、です。PSR-4では、クラス名の名前空間の階層のトップが必要で「ベンダー名前空間」と呼ばれる(The fully qualified class name MUST have a top-level namespace name, also known as a “vendor namespace”.
)なんてことを言ってやがります。
逆説的に、相互運用性なんてものが関係ない文脈なら、必ずしも几帳面に名前空間を切ってやる必要なんてないのです。\App\Hoge
だろうが\Hoge_Fuga
だろうが\User
だろうが、文句をつけられる義理がある人なんて(自分たちのチームメンバー以外には)居ないのです2。
筆者が仕事で携るプロジェクトのアプリケーションでは\User_Common
や\Image_URL
のようなクラス名が一般的なクラス名の規則として受け入れられて居ります。あなたがPHPに詳しいなら、いついかなるときでもnamespace
で区切ることが最適なソリューションであるとは限らないと理解いただけますね?
もちろん、ライブラリとして一般公開されるようなパッケージでは相互運用性はとても重要です。あなたがライブラリをPackagistで公開するつもりなら、PSR-4(あるいは、廃止済みのPSR-0)に準拠することを強く推奨します。
余談ですが、PSR-4またはPSR-0に準拠すると自動的に「1ファイルにつき1クラス」になります。むかし「ファイル読み込みが細分化されて非効率」のような主張を見掛けた気もしますが、一般的な運用環境ではOPcacheが有効化されてるだろうし目に見える差は出ないんじゃないですかね3。
コーディングスタイル
これは「標準」として「勧告」する意味が本当にあるのかと議論を喚ぶところではありますが、意義について以下のように説明されます。
Standardized formatting reduces the cognitive friction when reading code from other authors.
標準化されたフォーマットは、他者がコードを読む際の認識の差異を減らします。
トノコト。よく読むとPSR-1とPSR-2は別の目的があることがわかりますので、要点を説明します。
PSR-1
PSR-1: Basic Coding Standard - PHP-FIG (基本コーディング標準) は、「高度な技術的相互運用」当然遵守されるべき規約といった位置付けです。……何を言ってるのか私もわかりませんが、この規約で言及される項目は「ベストプラクティス」と「命名規則」に分けられます。
以下、PSR-1 基本コーディング規約(日本語)|北海道札幌市のシステム開発会社インフィニットループの訳から引用します。
- PHPコードは「
<?php
」及び 「<?=
」タグを使用しなければなりません。- 文字コードはUTF-8(BOM無し)を使用しなければなりません。
- シンボル(クラス、関数、定数など)を宣言するためのファイルと、副作用のある処理(出力の生成、ini設定の変更など)を行うためのファイルは、分けるべきです。
- 名前空間、クラスについてはPSR-0に準拠しなければなりません。
- クラス名は、
StudlyCaps
(単語の先頭文字を大文字で表記する記法)記法で定義しなければなりません。- クラス定数は全て大文字とし、区切り文字にはアンダースコアを用いて定義しなければなりません。
- メソッド名は
camelCase
記法で定義しなければなりません。
このうち、前半の項目は運用しやすいPHPスクリプトとして守ることが強く推奨される項目です。
-
<?php
と<?=
以外のPHPタグはPHP7で削除されました - namespaceとBOMに何の関係があるのさのような問題を引き起こします
- 現在のインターネットでの標準的な文字コードはUTF-8なので、それ以外の文字コードは非推奨です
- PSR-4(またはPSR-0)でクラスが遅延ロードされたとき「特定のクラスがロードされると勝手に設定が変更されてしまう」ような現象が起きてしまっては、挙動がわかりにくくなる
個人的意見では、それ以外の項目は比較的どうでもいいです。クラス名とかメソッド名とかチームで好きに決めれば良いと思ってます。
このガイドでは、プロパティの命名規則として特定のスタイル(
$StudlyCaps
、$camelCase
、$under_score
など)を推奨することはしません。
メソッド名も同じように特定のスタイルを強制しないでも良かったんじゃないですかね……? しかし以下の記述は重要です。
どのような命名規則を使用するにせよ、適切なスコープ内において一貫性を持たせるべきです。 ここでのスコープは、ベンダーレベル、パッケージレベル、クラスレベルまたはメソッドレベルを指します。
プロジェクトメンバーが混乱しないように一貫性を持たせるのは大事です。この思想はPSR-2に続きます。
PSR-2
PSR-2: Coding Style Guide - PHP-FIG (コーディングスタイルガイド) は、PSR-1よりもさらに踏み込んだ、具体的な数多くの項目が挙げられます。インデント幅、改行位置、スペースの空けかた、修飾子の並べかたなどです。
このスタイルガイドに則って書かれたPHPスクリプトは以下のような見ための特徴を持ちます。
<?php
function hoge(Foo $foo, $delimiter)
{
if ($foo->bar() !== false) {
return piyo();
}
$f = function ($s) use ($delimiter) {
return "{$delimiter} {$s} {$delimiter}";
};
foreach ($foo->buz as $b) {
echo $b->format($f), PHP_EOL;
}
}
私はこのPSR-2スタイルが大好きなのですが、このスタイルは「縦に間延びしてかっこわるい」「改行位置が覚えにくい」などの批判に晒されやすいものでもあります。
この記事の焦点はまさにPSR-2についての誤解で、多くの読者はこのスタイルガイドの存在意義を誤解してます。以下、再びPSR-2 コーディングガイド(日本語)|北海道札幌市のシステム開発会社インフィニットループの訳より引用します。
スタイルルールは、様々なプロジェクトの共通内容から生み出されています。 様々な作者が複数プロジェクトを横断して協力しあうことで、全てのプロジェクトで有用なガイドライン策定の助けとなります。 従って、このガイド本来の利点は、ルール自体にはなくルールを共有することにあります。
PSR-2は飽くまで「スタイルガイド」であって、既存のPHPプロジェクトのコーディングスタイルの特徴を抽出して悪魔合体させたものに過ぎません。(実際の調査内容は付録のSurvey
として掲載されてます)
批判があるのは承知の上で、これを叩き台にしてプロジェクトごとにコーディングスタイルのガイドラインを策定させることが、フレームワークとしてのPSR-2の機能です。一見役割がわかりにくいPSR-1「基本コーディング標準」とは分離された別の文書として定義されたのは、このためです。
実際に、私のプロジェクトや複数の知人の会社などではPSR-2の以下のような項目をカスタマイズしたと聞きます。
- スペースではなく8タブでインデントする
-
switch case
のインデントを一段浅くする - 関数やクラス定義の
{
を独立した行にしない - 一行あたりの文字数の制限を撤廃して、式の途中で改行しない
これらのルールは、まさにチームメンバーが違和感を覚えにくいものに統一することがベストです。PhpStormではこれらのスタイルをすべて設定で細かく制御できます。
インターフェイス
PHP-FIGによって「相互運用性」が強調される背景には、これまでさまざまなプロジェクトによって独立して、時にはほかのプロジェクトのコンポーネントに依存しながらフレームワークのモジュールが構成されてきた歴史があります。
- Zend Framework Components
- Symfony Components
- The League of Extraordinary Packages
- Nette Framework
- Aura for PHP
- Laravel PHP Components
- FuelPHP
- A set of PHP libraries – Hoa
- Zeta Components
これまでPSRで策定されたインターフェイスは、どれもフレームワークを構成するために、あったらべんりそうなインターフェイスです。
- PSR-3: Logger Interface
- PSR-6: Caching Interface
- PSR-7: HTTP message interfaces
- PSR-11: Container interface
- PSR-13: Link definition interfaces
- PSR-15: HTTP Server Request Handlers
- PSR-16: Common Interface for Caching Libraries
よくわからないですけど「インターフェイスに対してプログラミングせよ、実装に対してプログラミングするな」みたいな設計原則があって、特定のクラスやライブラリに依存したコードを書くのではなくインターフェイスで定義された仕様に則って実装してやれば「いともたやすく依存関係を差替できるんじゃね?」みたいないい話4があります。
使って便利そうだったら採用してみて、よくわかんなかったら無視する、程度の距離感でいいんじゃないですかね。
最後にまとめ
PSRはモダンPHPの象徴とかではないので、便利だったら使って、よくわかんなかったら無視してよい。
脚注
-
個人的な感想はすごいPHPerKaigi 2018に行ってきた – USAMI Kenta @tadsan – Mediumに書きました。 ↩
-
この発言を間に受けてクラスの命名規則や階層構造がしっちゃかめっちゃかになっても筆者が責任をとることはできませんヾ(〃><)ノ゙ ↩
-
設定の不備によって
spl_autoload()
が走ってしまって本番サーバーに負荷を掛けてしまったことがありますが… Composerのクラスローダーを使ってたら問題は起こらないです。 ↩ -
実際PSR-7はHTTPを抽象化したものでHTTPクライアントとかHTTPサーバーの実装には十分なんだけど、PSR-15と組み合せて使ってみようとすると「Webアプリでよくあるユーザーセッションみたいなの持つ場所なくね?
$_SESSION
直接触っちゃっていいの?」とか、「これ後続のハンドラに渡さずに中断する方法が定義されてないけど、どうすりゃいいの? 雑に例外投げちゃっていいの?」みたいな仕様に粗が見えちゃって、仕様の範囲内の理想だけではうまくいかないんだなってことがわかってくるんですが。 ↩