Page 1
Phanによる
静的解析
PHPコード
公開補訂版
ピクシブ株式会社
うさみけんた @tadsan
JAPAN phpcon 2016, 3rd November
公開日:
by USAMI Kenta @tadsan
Phanによる
静的解析
PHPコード
公開補訂版
ピクシブ株式会社
うさみけんた @tadsan
JAPAN phpcon 2016, 3rd November
お前誰よ
うさみけんた (@tadsan) / Zonu.EXE
日常的なバグ修正から、アーキテクチャの改善
おしごと
現実に動いている
(ユーザーさんが利用している)
サービスを破壊せずに
コードの健全さを向上したい
そんなノウハウをギュッと詰め込んだ
PHP大規模開発入門
(連載中)
PHP大規模開発入門
Vol.87:
PHPDocでコードの品質を保つ
名前空間とオートローディング
Vol.94:
PHP初心者がハマりがちな落とし穴
……型のキャスト,変数とリファレンス
Vol.95:
PHPの静的解析
PHP大規模開発入門
Vol.87:
PHPDocでコードの品質を保つ
名前空間とオートローディング
Vol.94:
PHP初心者がハマりがちな落とし穴
……型のキャスト,変数とリファレンス
Vol.95:
PHPの静的解析
おしながき
PHPと型
PHPと型
動的型検査
プログラムを動かしながら(←動的)
手さぐりで型を調べる (←型検査)
$a = 13; $b = "2";
echo $a + $b;
$a int $b string
の型は だな、 の型は …
「
!」
だけど数字だから足しざんできるね
……みたいな茶番を毎回やってる
PHPの型
変数に型はないが、値が型を持つ
のような比較が成り立つ設計
PHPの型の種類
整数 浮動小数点数 論理値
int float bool
string array resource
( ) ( ) ( )
オブジェクト ヌル値
object null
( ) ( )
array
では のみ宣言に記述できる
int float string bool
7.0では , , , が記述できる
型宣言(PHP7)
PHP7の関数(メソッド・クロージャ含む)は、
引数と返り値に型を定義することができる
// PHP 5
function trapezoid($h, $a, $b) {
return $h * ($a + $b) / 2;
}
// PHP 7
function trapezoid(float $h, float $a, float $b): float
{
return $h * ($a + $b) / 2;
}
動的型検査の弱点
プログラムを動かしてみないとわからない
// 環境依存の分岐 (本番環境じゃないと動かない)
if (is_production()) {
echo $a + $b; // 検出できない
echo 1 + []; // ←これはPHP7で検出できるようになった
}
function f(): int {
return []; // これも検出できない
}
PHPDoc
#とは何か
DocCommentとリフレクション
定義文 に付属する
(クラス・関数・メソッドなど)
特別なコメント の を実行時に
/** */
( 範囲)
〜
文字列として取得することができる(DocComment)
/** ここはDocCommentだよ */
function hoge() {
}
$ref = new \ReflectionFunction('hoge');
echo $ref->getDocComment();
// => "/** ここはDocCommentだよ */"
PHPDoc型注釈
DocCommentに型定義を注釈として記述する記法。
PhpStormやPhanが解釈してくれる。
/**
* @param float $h
* @param float $a
* @param float $b
* @return float
*/
function trapezoid($h, $a, $b) {
return $h * ($a + $b) / 2;
}
PhpStormの型表示
PHPDocの型
基本はPHPの型かクラス名を書く
, , , , なども有効
なぜPHPDoc型注釈?
いきなり実装コードに型定義を追加すると、
PHPDocはただのコメントなので、
これだけ覚えて帰ってね
タグ名 意味 例
@param 引数を定義
@param int $n1
@return 返り値を定義
@return int[]
@var 変数/プロパティを定義
@var int
@property マジックプロパティを定義
@property int $id
@property
ふつうのプロパティは じゃなくて
@var
なので注意
@property
(Phanは 未対応)
@var
プロパティの例 ( )
@var
ふつうのプロパティの場合は を使って書く
final class Book {
/** @var string */
public $title;
/** @var \MyApp\Author[] */
public $authors;
/** @var \MyApp\ISBN */
public $isbn;
}
@property
プロパティの例 ( )
拙作のzonuexe/objectsystemを使った場合
/**
* @property string $title
* @property \MyApp\Author[] $authors
* @property \MyApp\ISBN $isbn
*/
final class Book {
use \Teto\Object\TypedProperty;
protected static $property_types = [
'title' => 'string',
'authors' => 'MyApp\Author[]',
'isbn' => '?MyApp\ISBN',
];
}
型注釈を使ったハック
複合型とコレクション(型の配列)を組み合せる
/** @return Book[]|\ArrayObject */
function getBooks() {
$data = hogehoge();
return new \ArrayObject($data);
}
$books = getBooks();
$books->▍ // ここで \ArrayObject の補完が効く
foreach ($books as $book) {
$book->▍ // ここで Book の補完が効く
}
Phanの紹介
Phanとは何か
ハンドメイドマーケットを運営する
アメリカのEtsy社が開発する静的解析ツール
https://github.com/etsy/phan
現在PHP作者のRasmus Lerdorf が所属し、
Phanの導入
https://github.com/etsy/phan/releases
ただしラッパースクリプトを用意すれば、
/path/to/php7/bin/php ${PHAN_BIN:-/path/to/bin/phan} "$@"
Phanの設定
はじめにWikiから設定ファイルをコピペして、
.phan/config.php
プロジェクトの に保存
https://github.com/etsy/phan/wiki/Getting-Started#creating-a-config-file
// ディレクトリホワイトリスト
'directory_list' => [
'src',
'vendor/symfony/console',
],
// ディレクトリブラックリスト
"exclude_analysis_directory_list" => [
'vendor/'
],
Phanの設定(そのほか)
タグ(ジェネリック) 有効/無効
(可変変数)を検出
Phan コマンド
-o, --output <filename>
出力ファイル指定
出力形式
デッドコード検出
PHP7互換性
並列実行数
進捗バーを表示
./phan-wrapper -m csv -o phan.csv -x -j4 \
--progress-bar --backward-compatibility-checks
Phan 出力
デフォルトの場合の出力
pixiv-lib/Illust/Common.php:738 PhanTypeMismatchArgumentInternal
Argument 1 (month) is string but \checkdate() takes int
pixiv-lib/Illust/Common.php:738 PhanTypeMismatchArgumentInternal
Argument 2 (day) is string but \checkdate() takes int
path/to/file.php:333 PhanTypeMismatchArgumentInternal This is message.
はIssueと呼ばれる
PhanTypeMismatchArgumentInternal
Phan Issueいろいろ
PhanParamTooFew PhanParamTooMany
/
/
PhanUndeclaredClassCatch
存在しないクラスでキャッチしようとしてないか
// PHP 5
namespace MyApp;
try {
foo();
} catch (RuntimeException $e) {
// ↑ \MyApp\RuntimeException
} catch (\Excaption $e) {
// ↑ \Exception が正しい
}
PhanUndeclaredClassInstanceof
instanceof
存在しないクラスで してないか
namespace MyApp;
$e = get_error();
if ($e instanceof RuntimeException) {
// ↑ \MyApp\RuntimeException
} elseif ($e instanceof \Excaption) {
// ↑ \Exception が正しい
}
PhanTypeMismatchForeach
foreachできない値をforeachしようとしてる
$iter = null;
// E_WARNING
foreach ($iter as $i) {
echo $i;
}
PhanTypeMismatchReturn
定義と違った型の値を返してる
/** @return void */
function f() {
return null;
}
function g():int {
return [];
}
PhanTypeMissingReturn
特定の型を返すべきだが、何も返していない
/** @return int */
function g(){
return;
}
PhanTypeVoidAssignment
null
実際には が代入されるが
void
は「データがない」を意味するので適切ではな
い
/** @return void */
function g(){ return; }
$result = g();
PhanNoopXXXX
無意味なコードを検出する
// 代入しない配列 PhanNoopArray
[1, 2, 3];
// 代入も実行もしないクロージャ PhanNoopClosure
function(){ return awesome(); };
// returnしていないので無意味 PhanNoopProperty
class C {
public $p;
function f() { $this->p; }
}
PhanDeprecatedFunction
廃止予定/非推奨のメソッドを利用。
古い実装を新しいものに置換する際に
@deprecated
を付けておくと洗い出せてべんり
function newAwesomeFunc(){}
/** @deprecated */
function oldFunc(){}
oldFunc(); // PhpStormは取り消し線で表示
Phan独自のアノテーション
@suppress <issue_type>
クラス・メソッドなどの範囲でIssueを無効化する
/** @suppress PhanUndeclaredConstant */
class C {
function f() { return AWESOME_CONSTANT; }
/** @supress PhanUndeclaredProperty */
function g() { return $this->p; }
}
Phan独自のアノテーション
@template
型変数(ジェネリック)
/** @template T1 */
class Container {
/** @var T1 */
private $value;
public function __construct($value){ $this->value =
$value; }
function getValue() { return $value; }
}
Phanの制限
define()
関数での定数定義はサポートされない
扱い
には対応してない
を使ったProxyパターン殺し
Phanのめんどくさいところ
重い
Phanの注意点
メソッド/プロパティの動的定義に未対応
, , , など
PHP用語としての「オーバーロード」
http://php.net/manual/ja/language.oop5.overloading.php
定数の動的定義にも未対応
はだめ。 文ならOK。
Phanの比較対象(競合)
定番:PHP Mess Detector (PHPMD)
まとめ
PHPDocの型注釈は動作に影響を及ぼさずに
Phanで静的解析することで、
廃止予定の非推奨メソッドには
をつける習慣をつけると殲滅が捗る