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カンファレンス

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拡張)
依存関係 PHP-Parser
Tolerant 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

初めてでも
できちゃった