Skip to content

PHPプロジェクトに静的解析を新規導入する

公開日:

東京都港区六本木株式会社メルカリで開催された『大改修!PHPレガシーコードビフォーアフター』でPHPの匠(30分)として発表しました。

Download PDF

スライドテキスト

Page 1

PHPプロジェクトに静的解析を新規導入するIntroduce static analyzers to PHP project

Page 2

お前誰よ

  • うさみけんた (@tadsan) / Zonu.EXE
  • GitHub/Packagistでは id: zonuexe
  • ピクシブ株式会社 pixiv事業本部
  • Emacs Lisper, PHPer
  • Emacs PHP Modeのメンテナ引き継ぎました
  • 好きなリスプはEmacs Lispです
  • Qiitaに記事を書いたり変なコメントしてるよ

Page 3

Page 4

Page 5

Page 6

本日のお題

Page 7

Page 8

Page 9

Page 10

さて

Page 11

PHPカンファレンス2016

Page 12

Page 13

まだユニットテストと同様に「あって当然」のものとまでは普及できてない

Page 14

Page 15

既存のプロジェクトに導入する話はあまりできてなかった

Page 16

Page 17

なぜ静的解析をするのか

Page 18

関数がどのような引数を期待しどんな値を返すのか明示したい

Page 19

プログラムが間違ってることを実行前に検証したい

Page 20

それ以上に

Page 21

オブジェクトのメソッドやプロパティが適切に補完されると気持ち良い

Page 22

補完がきちんと機能するようにPHPに型をつけていこう

Page 23

ただし

Page 24

静的解析は、現に「動いている」事実を覆すものではない

Page 25

エラーとして見えないが「たまたま動いている」現実を炙り出すことはある

Page 26

変更によって動かなくなってしまうことを「動かさずに」発見できるかもしれない

Page 27

静的解析基盤を整えた瞬間に致命的な問題を検出できることはまれ

Page 28

ということで静的解析をしましょう

Page 29

静的解析ツールの種類

Page 30

現行の高度な静的解析ツール

PhpStorm (IDE)

Phan

PHPStan

Psalm

Page 31

PhpStorm

よいところ

導入が最も簡単

十分な精度のエラー検出

よくないところ

エラーの列挙ができないのでCIのフローに組み込めない

Page 32

Phan

よいところ

導入は比較的簡単

とても高い精度のエラー検出

よくないところ

大規模プロジェクトでは解析に時間がかかる

Page 33

PHPStan

よいところ

実行時情報を使用し高速

高い精度のエラー検出

よくないところ

Composer以外の依存関係があると導入にコツが要る

Page 34

Psalm

よいところ

独自の引数アサーション

高い精度のエラー検出

よくないところ

大規模プロジェクトでは解析に時間がかかる

Page 35

静的解析の選択軸

Phan PHPStan

定義のみ実行が必要

解析方法

実行しない(静的)

(静的と動的のmix)

依存関係

php-ast (高速/C拡張)

Tolerant PHP Parser(低速)

PHP-Parser

対象ファイルを

解析単位プロジェクトおよび依存ライブラリ全体

個別指定

Page 36

つまりどれがいいのか (CI)

現時点ではそれぞれのツールで検出項目に一長一短があるため、使い分けるとよい

PHPStanは高速なのでGitでブランチにpushするごとに検査するようなCIと相性がよい

Page 37

PHPStanの制約

クラス/関数/定数の定義と副作用のある処理は明確に分かれていないといけない

つまり、PSR-1

Phanなら定義と処理が混在したファイルも解析できる

Page 38

こういうコードはPHPStanで解析できない

<?php include __DIR__ . '/config.php';

$connection = new PDO(DSN);drop_table($connection);exit;

function drop_table($connection){$connection->exec('DROP TABLE `users`;');}

Page 39

静的解析を導入しよう

Page 40

Page 41

Page 42

Page 43

CakePHP 4.0.0ではPHPStanとPsalmで型定義が大幅に改善(現在RC1)

Page 44

じゃあPhanを入れるか

Page 45

基本はかcomposer require Pharファイルをダウンロードするだけ

Page 46

forteeはレガシーを自称しているがDocker Compose

Page 47

Docker Compose使ったことない僕の方がレガシーなんですけど…

Page 48

Docker Phan

[検索]

Page 49

🙅

だめです

Page 50

Page 51

Page 52

古すぎる!

Page 53

Phanはほぼ毎週アップデートされてさまざまな改善がある

Page 54

最新のPhanだけが良いPhanだ

Page 55

Phanの要求環境

基本的に普通のPHPイメージ

ext-ast (PECL ast)は必須

Xdebugは無効化したい

パフォーマンスに影響する

ext-pcntl, ext-iconv,ext-mbstringも推奨

Page 56

Dockerfileにphp-astを足す

RUN pecl install ast \&& docker-php-ext-enable ast

Page 57

astをデフォルト有効化したくなければ

RUN pecl install ast

Page 58

別のコンテナイメージに分割したい場合

Page 59

docker/phan/Dockerfile

FROM php:7.3-cli RUN apt-get update -y \&& apt-get install -y --no-install-recommends \libicu-dev \unzip RUN docker-php-ext-install -j$(nproc) intl pcntl

RUN pecl install ast && docker-php-ext-enable ast

COPY --from=composer /usr/bin/composer \/usr/bin/composer

RUN composer global require phan/phan ENTRYPOINT php /root/.composer/vendor/bin/phan WORKDIR /var/www/html

Page 60

docker-compose.yml

version: "3.7"services:phan:build: ./docker/phan volumes:- ./:/var/www/html:cached entrypoint: php /root/.composer/vendor/bin/phan working_dir: /var/www/html

Page 61

独立したコンテナに分けるメリットも大きくない気がするので既存のPHP用コンテナに混ぜ込めばいいと思います

Page 62

composer require --devを使うべき?

好みでどうぞ

アプリケーションと依存関係が混ざるのでメンテ難しいと思います

僕はサブディレクトリにcomposer.json作ります

composer-bin-pluginを使ってもいいかも

Page 63

以後./phan というラッパースクリプトを使うことにします

Page 64

./phan

#!/usr/bin docker-compose run --rm php-cli \.phan/vendor/bin/phan "$@"

Windowsならphan.batってファイルを作って %* あたりでパラメータ展開すれば動くと思うけど、詳しいことはWindowsに詳しいひとに任せた!

Page 65

phan --initで初期化できる

.phan/config.phpが生成される

自動追加されなかったdirectory_listを追加していく

composer requireで直接依存するパッケージだけが自動追加される

Page 66

基本的な使いかた

./phan

-o phan.log結果をphan.logに書き出す

-j 4並列実行数。CPUのコア数を指定するのが良い (諸説あり)

Page 67

baseline

--save-baseline=.phan/baseline.php を付けて実行するとファイルごとに検出されたIssueを保存できる

--load-baseline で実行すると新たに増えたものだけ報告される

Page 68

suppress_issue_types

報告しないIssueをリストアップ

フレームワーク側に問題があるなら握り潰すしかない…

ある程度の妥協も時には必要

CakePHP 4.0になればおそらくほぼ消せるでしょう

Page 69

tadsanのよくやる手法

masterブランチの内容をphan-master.logに書き出す

リファクタリングして変更の都度解析結果をphan.logに書き出す

diff -u phan-master.log phan.log

Page 70

実際に検出されたミス

Page 71

未定義変数の検出

$part変数が未定義

Page 72

未定義変数の検出

$videoEmbedHtmlと$slideEmbedHtmlが未定義

Page 73

deprecatedコードの検出

CakePHP 4.0.0でプロパティにアクセスできなくなる

Page 74

未定義クラスの検出

use Exceptionがないので現在の名前空間を指してる

Page 75

おまけ: 謎のException

3箇所くらいで登場

どれもAuraを直接使ってない

たぶんPhpStormの自動挿入

Page 76

コンストラクタで値を返す

値を返せない(単にreturn;でよい)

Page 77

そろそろまとめ

Page 78

実際稼動しているアプリなので致命的な問題はなかなか見つからない

Page 79

リファクタリングやフレームワークのバージョンアップでは大きな武器になる

Page 80

地味なものも目立つが地道に潰していこう

Page 81

おまけ

Page 82

今回forteeのコードで初めてDocker Composeで構築されたPHPアプリケーションに触れた

Page 83

🤔

なるほどわからん

Page 84

手持ちのレガシーPHPをDocker Composeに載せてみるか

Page 85

Page 86

Page 87

Vagrant →Docker Compose

Page 88

phpdotenv

.env → conf/.env

Page 89

docker-composeを噛ませてsetupスクリプトを実行できるように場所を変更

Page 90

php -S →Apache HTTP

Page 91

初めてでもできちゃった