Page 1
PSRを実装してみる
Let's implement PSR
(PHP Standards Recommendations)
2019-01-26 PHP Conference Sendai
Sendai, Miyagi, Japan
#phpconsen
公開日:
by USAMI Kenta @tadsan
宮城県仙台市青葉区花京院のTKPガーデンシティPREMIUM仙台西口8Fで開催された『PHPカンファレンス仙台2019』でレギュラーセッション(30分)として発表しました。
PSRを実装してみる
Let's implement PSR
(PHP Standards Recommendations)
2019-01-26 PHP Conference Sendai
Sendai, Miyagi, Japan
#phpconsen
お前誰よ
うさみけんた (@tadsan) / Zonu.EXE
今回の目的
PSRって何の ためにあるの
強い人たちと話すと
PSRの不満についての 愚痴を聞くけれども、
なるほどわからん
わかりたい
それを明らかにしたい と思い、われわれ取材 班は仙台へと旅立った
でも、英語も
仕様書を読むのも
苦手だお…
|
\ __ / ピコーン
_ (m) _
そうだ、実装しながら理解しよう
|ミ|
/ `´ \
('A`)
ノヽノヽ くく
実装
仕様を雑に読む→
よくわからないので
ぐぐる→
既存実装を読んでみる→
雑に実装してみる→
(以下ループ)
背景
PHPのコンポーネントを 作って提供してる組織は
意外とたくさんある
たくさんあるのはいいが
大同小異、同じような
ものが乱立してると困る
使い回せるものが
あるなら折角だから
相互運用したい
そのような現状でPSRは 何を提供しているのか、
実装しながら見てみよう
おしながき
PHP-FIGについて
PSR-0 PSR-3 PSR-1 PSR-6 PSR-2 PSR-13
そのほかのPSR
ここから
駆け足になるので
最初にまとめ
PSRにナンバリング
されてるからと言って
高品質で優先すべき
対象とは限らない
PHP-FIG
フレームワーク相互運用グループ
PSRを策定
する団体
フレームワーク/CMS/
そのほかPHP関連
開発者の寄合所帯
さっき挙げた団体 の全てが加盟して
るわけではない
抜けちゃったと ころもいろいろ
ある
Zend,CakePHP,
Yii, Drupal, Slim
など多数
Guzzle, Doctrine, Symfony, Laravel, The PHP Leagueは
脱退済み
脱退したからと言っ てPSRを尊重して ないわけではない
PSR
PHP標準勧告
「勧告」と名前はついて るがPHP本体とは関係 なく、有志による規格
グループの背景にある考えは、代表者たち がプロジェクト間の共通点について話し合
うことで協働する方法を探ることです。
グループの議論の主な観客は参加プロジェ クト同士ですが、そのほかの コミュニ
PHP
ティも注目していることはよく承知してい ます。ほかの人々が の成果物を採用す
PSR
ることは歓迎しますが、それは目的ではあ
りません。
投票メンバーは標準を遵守しなければいけ
ませんか?
いいえ。 の投票メンバーになるこ
PHP-FIG
とで、すべての、あるいは任意の を実
PSR
装することを強制されることはありません。 プロジェクトはしかるべき時にアップグレー ドする際に後方互換性を考慮する必要があ ります。ほとんどのプロジェクトで最終的 に採用されると想定されますが、必須では
ありません。
勧告される対象は我々
下々のPHPer各位
ではなくPHP-FIGに
任意で参加した団体
勧告された参加団体が
PSRを準拠することも
また任意
脱退したはずの団体が
PSRを準拠することも
また任意
PSR自体が、ベスト
プラクティスを標榜し てたりもするけど…?
それはそれ
これはこれ
PSR-0
Autoloading Standard
失効済み!!!
PSR-0
Autoloading Standard
オートロードに適合 したクラスの命名規 則とファイルの配置
前提
PHP 5.0からクラスのオート
ローディング(遅延ロード)に対応
=必要あるまで読み込まない
それ以前は から
include_path
が主流
include/require(_once)
ざっくりPSR-0
クラス名の構造の規則
\<Vendor Name>\(<Namespace>\)*<Class Name>
区切りの擬似名前空間も許容
_
\<Vendor Name>_(<Namespace>_)*<Class Name>
ディレクトリ配置
.
Hoge
!""
Fuga
!""
Piyo.php
!""
class Hoge_Fuga_Piyo {}
または
//
namespace Hoge\Fuga;
class Piyo{}
実装依存になる部分
ファイルシステム依存
大文字小文字の差異
Unicode NFD問題
曖昧な部分
「ベンダー名」って何…?
自由に決められるので、
ただの紳士協定に過ぎない
トップレベルのクラス定義を
戒める効果はある(?)
実装方式
A: クラスマップ方式
実行前にディレクトリ構造を
スキャンして対応表を作成
B: 逐次確認
実行時にファイルシステムを検査
PSR-0準拠クラスローダー関数
function add_directory(string $base_dir): void {
$directory = rtrim($base_dir, '/\\');
spl_autoload_register(function ($fqcn) use ($base_dir) {
$namespaces = explode('\\', $fqcn);
$class = array_pop($namespaces);
$class_file = strtr($class, ['_' => '/']) .
'.php';
$ns_dir = implode('/', $namespaces);
$file_path = implode(‘/',
array_filter([$base_dir, $ns_dir, $class_file], 'strlen'));
if (is_file($file_path)) {
require_once $file_path;
}
});
}
現状
以前はライブラリごとに
クラスローダーを提供してた
など
Requests::register_autoloader()
最近はComposerがオートロー ド機能を提供してくれるので
composer.jsonに書くだけ
重要
PSR-0はPSR-4で代替された
ので廃止済み
Composer以前
PEARでインストールする zipをダウンロードして、
そのディレクトリを
include_pathに追加する
同じホスト上で動作する別のPHP プロジェクトと共用されることも
Composer以後
依存パッケージはプロジェクト内 のサブディレクトリにインストール
同じホスト上で動作する別の
プロジェクトと混在すること
は原則ない
PSR-1
Basic Coding Standard
基本的な(=相互運用 のため重要度の高い)
コーディング規約
要件
名前空間・クラス名は
PSR-4/0に適合するように
文字コードはUTF-8(BOM無)
クラス定義で副作用を
起こしてはいけない
命名規則
クラス名は
UpperCamelCase
定数名は
UPPER_SNAKE_CASE
メソッド名は
lowerCamelCase
変数・プロパティは規定しない
理由
「あるクラスがロードされた副 作用として別のファイルが読 み込まれる」のような挙動は
わかりにくい
最低限のもの以外はinclude/ requireせずに遅延ロードに
任せるのが無難
こういうコードはだめ
<?php /** Foo/Bar.php */
// 別のファイルを読み込もうとする
include_once __DIR__ . "/foo.php";
// エラーレベルを変更
error_reporting(0);
class Foo
{
// ...
}
「実装します」
といった感じの
ものではないので
次
PSR-2
Coding Style Guide
コーディング
スタイルの
ガイドライン
要件
プロジェクト内で表記を一貫 して意識のずれを低減する
PSR-1に準拠することが前提
さまざまなプロジェクトのコーディ ングスタイルを調査した上で策定
ホワイトスペース・インデント
インデントは4スペースが基本 単項演算子以外の演算子や() の前後にはスペースを入れる
改行コードはLF
一行あたりの文字数は80文字 を推奨、120文字以上は警告
ざっくり
class Foo
← 改行する
{
public function bar($xs)
← 改行する
{
foreach ($xs as $x) {
← 改行しない
$f = function ($a) use ($x) {
← 改行しない
// ...
};
}
}
}
{} と改行位置の癖
宣言文
( , , など)
class function namespace
の開始行と の間は改行する
{
それ以外の文 は
( , など)
if foreach
改行せず、開始行に を書く
{
無名関数は文ではなく式なので
改行せず、開始行に を書く
{
重要なこと
スタイルルールは、様々なプロジェクトの 共通内容から生み出されています。様々な
作者が複数プロジェクトを横断して協力し あうことで、全てのプロジェクトで有用な ガイドライン策定の助けとなります。従っ
て、このガイド本来の利点は、ルール自体 にはなくルールを共有することにあります。
http://www.infiniteloop.co.jp/docs/psr/psr-2-coding-style-guide.html
その他本スタイルガイドでは、意図的に
7.
省略しているスタイルやプラクティスが多 くあります。例えば下記のような幾つかに
ついては、ここでは明記していません。
(中略)
なお、本スタイルガイドは将来的に様々な スタイルやプラクティスの登場に応じて改
定・拡張をできるものとします。
http://www.infiniteloop.co.jp/docs/psr/psr-2-coding-style-guide.html
PSR-2は
コーディングスタイル策定の
ガイドライン
PSR-2をカスタマイズする
定義文であっても { を同じ行
で書くとか
インデントを8タブにするとか 改行コードをCR+LFにするとか
策定したガイドラインを守らせる
PHP-CS-Fixerを導入する EditorConfigを導入する
PhpStormの設定ファイルを共有
StyleCI
https://styleci.io
PHP-CS-Fixerの設定例
<?php
$finder = PhpCsFixer\Finder::create()
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests');
return PhpCsFixer\Config::create()
->setRules([
定義開始行と { を同じ行に
'@PSR1' => true, '@PSR2' => true,
'braces' => [
'position_after_functions_and_oop_constructs' => 'same',
],
])
->setFinder($finder)
タブでインデント
->setIndent("\t")
->setLineEnding("\n");
.editorconfig
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
# PHP - http://php.net/
[*.php]
タブでインデント
indent_style = tab
幅8で表示
indent_size = 8
PhpStormの設定ファイルを共有
PhpStormが生成する は
.idea
一部ファイルを除き、
共有した方がいいらしい
github/gitignore: JetBrains.gitignore
特に
.idea/codeStyles/Project.xml
がコーディングスタイル
大事なこと
コーディングスタイルは人間が
レビューであれこれと指図したり
するよりも、機械的に変換した ほうがギクシャクせずに済む
「人間が気をつける」は破綻する 「自然にそうなる」 状態が理想
PSR-2の現状
初版が2012年6月なので、
PHP7の時代には不足気味
可変長引数、無名クラスとか
現在、後継となるPSR-12
が
Extended Coding Style Guide
策定作業中
PSR-3
Logger Interface
Syslog風の
Loggerインター
フェイス
要件
Syslogで定義された8種類の
レベルごとにログを記録
緊急度が高い順から
Emergency, Alert, Critical,
Error, Warning, Notice,
Informational, Debug
実装
psr/logパッケージに含まれる
/
LoggerTrait AbstractLogger を利用して最小限で実装可能 今回実装したPSRのなかでは
機能がわかりやすく実装が
簡単なので初心者にオススメ
雑感
普通はMonologを使えばいい ので代替実装に変更するメリッ
トが全然感じられない
Monologのハンドラーを実装 した方が拡張しやすいので、
そちらの方がオススメ
さらに雑感
WebアプリからSyslogの
8種類のレベルはミスマッチ感
がある
正直、Error, Debug, Infoの
3レベルくらいにまとめた方が
扱いやすいのでは
ヘルパー関数
<?php declare(strict_types=1);
namespace ZoPsr\Log3;
use Psr\Log\LogLevel;
function to_num(string $level): int {
return [
LogLevel::EMERGENCY => 0, LogLevel::ALERT => 1, LogLevel::CRITICAL => 2, LogLevel::ERROR => 3, LogLevel::WARNING => 4, LogLevel::NOTICE => 5, LogLevel::INFO => 6, LogLevel::DEBUG => 7,
][$level];
}
行区切りJSON出力するロガー
final class FileLogger implements LoggerInterface {
use LoggerTrait;
/** @var resource */
private $_fp;
/** @var string */
private $_min_level;
/**
* @param resource $fp
* @param ?string $min_level
*/
public function __construct($fp, $min_level = null) {
$this->_fp = $fp;
$this->_min_level = $min_level ?? LogLevel::WARNING;
}
public function log($level, $message, array $context = []) {
if (to_num($level) <= to_num($this->_min_level)) {
$log = json_encode(['level' => $level, 'message' => $message, 'context' => $context],
JSON_UNESCAPED_SLASHES) ?: '{"json_encode_error":true}}';
fputs($this->_fp, $log); fputs($this->_fp, "\n");
PSR-4
Autoloader
オートロードに適合 したクラスの命名規 則とファイルの配置
PSR-0の後釜
オートロードに適合 したクラスの命名規 則とファイルの配置
PSR-0との差異
_を使った擬似名前空間は
許容されなくなった
PSR-0は名前空間に対応する ディレクトリを用意しなけれ ばならなかったが、PSR-4で はディレクトリを浅くできる
PSR-0との差異
prefixとbase directory
という概念が導入された
というクラス
\A\B\C\D
PSR-0:
src/A/B/C/D.php
PSR-4:
src/D.php
Composerを使ってたらお馴染
ディレクトリ配置
. .
src src
!"" !""
Fuga Piyo.php
!"" !""
Piyo.php
!""
<?php
namespace Hoge\Fuga;
class Piyo{}
クラスローダの実装(1)
final class ClassLoader {
/** @var array<string,string> */
private $directories = [];
public function add(string $prefix, string $directory): void {
$this->directories[$prefix] = $directory;
}
public function register(bool $prepend = false) {
spl_autoload_register([$this, 'autoload'], true, $prepend);
}
public function autoload(string $class): void {
$path = $this->_resolve($class);
if ($path !== null) { require_once $path;
}
}
クラスローダの実装(2)
private function _resolve(string $class): ?string { foreach ($this->directories as $prefix => $dir) {
if (strpos($class, $prefix) !== 0) {
continue;
}
$path = $this->_makePath($class, $prefix, $dir);
if (is_file($path)) {
return $path;
}
}
return null;
}
private function _makePath($class, $prefix, $directory): string {
$relative = substr($class, strlen($prefix)); $path = strtr($relative, '\\', '/') . '.php';
return $directory . DIRECTORY_SEPARATOR . $path;
}
}
ディレクトリの追加
<?php
use ZoPsr\Autoload4\ClassLoader;
use function ZoPsr\TestHelper\capture;
require_once __DIR__ . '/../../vendor/autoload.php';
$loader = new ClassLoader; $loader->register(false);
$loader->add('Foo\\', __DIR__ . '/1'); $loader->add('Hoge\\', __DIR__ . '/2');
PSR-4を実装する意味はあるか
クラス名の規則として重要 クラスローダは自作せずに
Composerを使った方がパフォー
マンスの最適化できて便利
PHPのクラスロードの仕組みを
学ぶ上では重要
PSR-13
Link definition interfaces
要件
ハイパーメディアの「リンク」
を抽象化したクラス
HTMLのとか とか
<link>
HTTPのLinkヘッダとか
(RFC5988/8288 WebLinking)
……なんだけど
仕様
基本はPSR-7と同じ
イミュータブルなオブジェクト
関連するRFCがやたらある
特にRFC6570(URI Template)、
それ混ぜる必要あったか…?
実装として混ざってるわけではない が、 という状態がある
isTemplated()
URI Template
RFC6570
URIに変数展開するための
テンプレート言語
これ自体は需要ある規格だが、
Linkでテンプレートありなしの hrefを混在させて嬉しいことは
……ある?
既存実装
crell/htmlmodel
PSR-13提案者の概念実証
php-fig/link-util
PHP-FIGによる実装
Linkクラスも実装されてる
あのSymfonyも独自実装してない
仕様への文句
isTemplated()
必死にユースケースを考えてみ たけど、積極的にこの分岐をし たいケースが一切見当たらない
でも事実上
Symfony\WebLink
黙殺されてるように見える
実装への文句
isTemplated()
hrefに か のどちらかが含
{ }
まれてたらURI Template文字
列って判定してる
すごく怪しい
URIの仕様
https://example.com/{ のよ
うなURIは完全に合法
{と}はreservedな文字でも unreservedな文字でもない ので、パーセントエンコード
されてるとは限らない
とてもバグりそうな雰囲気が
文字列を場当たり的な判定し て実行時に分岐するのは、
どう考えてもアンチパターン
せめて
TemplatedLinkInterface
のような別の型で表現するなら
気持ちはわかる
(けど嬉しくはない)
そのほかのPSR
ここから実装が間に 合わなかったPSRに ついて雑に話します
PSR-7 message interface
HTTPのリクエスト/レスポン
スを抽象化するクラス
GuzzleのようなHTTPクライア
ントライブラリには好適
しかしHTTPミドルウェアの
媒介としては辛くないですか
PSR-7とわたし
Guzzle使ったらPHPでめちゃんこ 簡単にHTTPプロキシ鯖を実装で
きたので好き
PHPでプロキシサーバーを作る
PHPのプロキシサーバーを説明する
PSR-6 Caching Interface
実は今回は実装してみたけど、
特に感情がない
業務ではRedisなどのKVSをラッ プした独自の抽象クラスを使っ てるけど、移行する気は特に
湧かなかった
PSR-5 PHPDoc (Draft)
PSRの番号が若いのに長年マー
ジされてない代表格
放棄されかけたがAPIドキュメ ント自動生成よりもIDEや静的 解析のデファクト標準として
重要なので議論復活した
PSR-5とわたし
WEB+DB PRESS Vol.87に書いた
(総集編に収録されてるから買って) 2018年のPHPDoc事情とPSR-5 PhpStorm 2018.3でPSR-5ジェネ リクス記法はサポートされない
PSR-5と静的解析
コード上の記述とPHPDocのコメン トを手がかりにして、怪しい部分
を高精度で検出してくれる
Phan, PHPStan, Psalm,
PhpStormなどのツールがある 今回の実装でも助けられました
PSR-8 Mutually Assured Hug
2014年4月1日に提案された
「この標準は、オブジェクトが互いの感 謝と支持を表明するための、一般的な
方法を確立します」
こんなのにPSRの番号の帯域を
降ってよかったの…?
PSR-8 Hug (日本語訳)