PHP ここで差がつくエラー処理 完全版

公開日:

Download PDF

スライドテキスト

Page 1

ここで差がつくエラー処理

By Error Handling your application will be completed

公開用完全版

, 8th Oct

PHP Conference Japan 2017

#phpcon2017

Kamata Tokyo, PiO

Page 2

2017年10月8日に開催された

PHPカンファレンスで発表した

「ここで差がつくエラー処理」の

完全版

発表資料を補訂した です

Page 3

お前誰よ
うさみけんた (@tadsan) / Zonu.EXE

  • GitHub/Packagistでは id: zonuexe
    • ピクシブ株式会社技術基盤チーム (pixiv.net)
  • Emacs Lisper, PHPer
  • 入社前は自宅警備をしながらRuby書いてた
    • Emacs PHP Modeのメンテナ引き継ぎました
    • Qiitaに記事を書いたり変なコメントしてるよ

Page 4

Page 5

We are hiring!

Page 6

はじめに

Page 7

みなさん困ってませんか

Page 8

Page 9

こんな画面を見せて
しまってませんか

Page 10

Page 11

本番サービスで
見せちゃってませんか

Page 12

Page 13

HTML 出力途中で
落ちたりしませんか

Page 14

Page 15

エラーになっても
Webサイトの体裁は

なんとか保ちたい
……ですよね?

Page 16

画面はpixivの実在のバグではなく 故意に例外を発生させたものです

Page 17

アジェンダ

Page 18

1. 例外・エラーの種類
2. 本番環境でのエラー処理 3. 開発環境でのエラー処理

Page 19

具体的にはこんな話をします

エラーと例外の基本概念

それらを制御するやりかた

開発環境でうんざりしない方法

Page 20

参考資料

だいたいPHPマニュアルに載ってる

http://php.net/language.operators.errorcontrol
http://php.net/set_error_handler
http://php.net/set_exception_handler
http://php.net/register_shutdown_function
http://php.net/ErrorException http://php.net/display-errors
whoops!

https://github.com/filp/whoops

Page 21

やらない話

エラー監視

例外設計

そもそもエラーって何だ

Page 22

PHPのコードの試しかた

php -r 'var_dump(1 + []);'

php -a

REPL (PsySH または )

https://3v4l.org/

多くのバージョンで串刺し実行

Page 23

エラー・例外とは

プログラムが正常ではないときに、
それを通知する仕組み

PHPではエラー例外がある

PHP5では処理系は基本的にエラー

PHP7で例外を投げることが増えた

Page 24

1. 例外・エラーの種類
2. 本番環境でのエラー処理 3. 開発環境でのエラー処理

Page 25

エラー

Page 26

エラー

従来のPHPで異常状態を通知する
ための仕組み

関数呼び出し 未定義変数の参照

結構いろんなところで発生する

PHP で種類が整理された

Page 27

主要なエラー

Notice

よくない処理の通知

Warning, Error

実行時の警告・エラー

Deprecated, Strict

非互換/廃止予定の機能

Fatal Error

致命的エラー

Page 28

Notice

配列の未定義位置から値を取り出 そうとしたときなどに発生する

信号無視みたいな軽犯罪

ただしバグの温床になるので、 無視せずきちんと対処を強く推奨

Page 29

Noticeの例 (一部)

echo $i; //

存在しない変数

echo $_GET['id']; //

存在しないインデクス

echo $o->abc; //

存在しないプロパティ

Page 30

Warning, Error

何かが「間違ってる」ときに発生

デフォルトの挙動ではWarningで
は停止せずに進む

一貫性がないが、どちらも異常状 態なので無視せず直した方がよい

Page 31

Warning, Errorの例(一部)

//

存在しないファイルを開く

$f = fopen("/hoge.txt", "r");

foo($i); //

引数が足りない、PHP5のみ

// ArgumentError
PHP7では に変更

function foo($i, $j) { echo $i, $j; }

Page 32

Deprecated, Strict

廃止予定や互換性の怪しい機能

split()

例: 関数

explode()
( とは別物)

5.3でdeprecated、7.0で削除

Strictは7.0で廃止(再分類)された

https://wiki.php.net/rfc/reclassify_e_strict

Page 33

Deprecated, Strictの例(一部)

class Hoge {

/** */
古いスタイルのコンストラクタ

function Hoge() {

}

}

Page 34

Deprecatedの例(一部)

//**

PHP5.3でDeprecatedエラー、7で削除

$a = split(',', '1,2,3');

//

エラー抑制演算子、通称なると
// エラーを握り潰せるべんりで危険なやつ

$a = @split(',', '1,2,3');

Page 35

Fatal Error (致命的エラー)

これ以上は続行できないエラー

同名のクラスや関数を
多重定義しようとしたとき

require

ファイルを できなかったとき

明らかに不正な式

Page 36

Fatal Errorの例(一部)

//

PHP5系とPHP7系でエラーになる

//

タイミングが異なるコード

$i = mt_rand(1, 2);
echo $i;

if ($i === 1) {

//

数値と配列は加算できない

$x = 1 + [];

}

Page 37

Fatal Errorの例(一部)

//

PHP5系とPHP7系でエラーになる

//

タイミングが異なるコード

$i = mt_rand(1, 2);
echo $i;

: 何も出力されずFatal

PHP7

if ($i === 1) {

: 「1」とFatal

それ以外
  または「2」のみ

$x = 1 + [];

}

Page 38

PHP7は賢いので、実際に

実行されることがなくても
(絶対実行されないifの中だろうと)

コンパイル時に検出してくれる

Page 39

例外

Page 40

Exception Throwable
例外 ( , )

PHP5で追加された言語機能

throw

異常状態のときに で投げる

たいいき(Non-local)
大域脱出と呼ばれる機能

catch

文で捕捉できる

Page 41

例外オブジェクト

PHP5ではExceptionまたは、 Exceptionを継承したクラス
PHP7ではThrowableインターフェ

イスと、Exceptionを継承しない
Errorとその派生クラスが追加 便宜上まとめて「例外」と呼ぶ

Page 42

ErrorとエラーとE_ERROR

PHP5系でエラーが発生したものが、
7系では例外(Errorクラス)を

throw

するように徐々に移行
すべての移行されたわけではなく、

まだエラーと例外の概念を両方
知っておく必要がある

E_ERROR

レベルのエラー…ややこしい

Page 43

エラー
ハンドリング

Page 44

伝統的なエラー処理手法

//

MySQLに接続失敗したら異常終了

$db = mysql_connect() or die("接続に失敗しまし
た");

die() は exit() と同じ意味

ここでいきなり強制終了する

そのあと処理はほとんどできない

Page 45

せめて例外に

if (do_something() === false) {

throw new FooException('do_something() が失敗した');

}

現在実行中の処理を中断して、
try…catch まで遡る

catchできなかったら、そこで
おとなしく野垂れ死ぬしかない…?

ただちに終了されないので、
まだ挽回の余地がある

Page 46

エラーハンドリング

発生したエラーをなんとかする

Page 47

エラーハンドリング

発生したエラーをなんとかする
A: どうにか続行を試みる
B: どうにもならないので諦める

Page 48

エラーハンドリング

発生したエラーをなんとかする
A: どうにか続行を試みる
B: どうにもならないので諦める
せめてログだけでも…

なんとか共通ナビゲーションと ロゴだけは表示してほしい…

Page 49

我々に三回のチャンスがある

set_error_handler()

set_exception_handler()

register_shutdown_function()

Page 50

error_reporting()

「出力するエラーの種類を設定する」

どのエラーレベルに対応するか
ビットフラグで設定する

引数なしで呼ぶと現在の値を返す

error_reporting(E_ALL & ~E_NOTICE)

Page 51

error_reporting() 設定方法

//

無視できるもの全部無視

error_reporting(0);

//

全部のエラーを捕捉する

error_reporting(E_ALL);

error_reporting(E_ALL | E_STRICT); //

PHP 5.3以下

//

Noticeのみ無視

error_reporting(E_ALL & ~E_NOTICE);

//

NoticeとDeprecatedを無視

error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);

Page 52

set_error_handler()

エラーが発生したときに、

set_error_hanlder()

に登録した

関数が呼ばれる
エラーに応じて処理を続行させる こともできるし終了させてもいい
Fatal errorは捕捉できない!

Page 53

set_exception_handler()

例外がtry-catchでも捕捉されな

かったときに、

set_exception_hanlder()

登録した関数が呼ばれる

関数が実行された後は停止する

登録できる関数はひとつだけ

Page 54

register_shutdown_function()

PHPスクリプト終了時に必ず
呼ばれる関数を登録する

set_error_handler()

に来ない

Fatal Error(致命的エラー)を
捕捉してロギングできるチャンス
最後のエメラルドスプラッシュ

Page 55

定石

Page 56

エラー処理の定石

デフォルトのエラー出力を切る

エラーは例外に変換して、
未捕捉の例外とまとめて処理する

処理すべきエラーかどうかの判定

error_reporting()
に を利用する

Page 57

デフォルトのエラー出力

Page 58

デフォルトのエラー出力

//

必ず読まれるPHPファイルで設定する

//

少なくとも本番環境では絶対offにする

ini_set('display_errors', 'off');

Page 59

set_exception_handler() 設定方法

//

例外をハンドリング

set_exception_handler(function ($e){

//

例外をログに記録

YourApp::logException($e);

//

エラー時の画面を表示

YourApp::displayErrorPage($e);

});

Page 60

エラー抑制とerror_reporting

は一時的に

(エラー抑制演算子)

error_reporting(0)

に設定して

処理を実行するだけ
独自設定したエラーハンドラが

error_reporting()

を考慮してくれ

ないと、エラーが握り潰されない

Page 61

set_error_handler() 設定方法

set_error_handler('my_error_handler');

function my_error_handler (

$level, $message, $file = null, $line = null) {

//

エラーレベルのAND(ビット積)をとる

if (error_reporting() & $level > 0) {

throw new ErrorException(

$message, 0, $level, $file, $line);

}

}

Page 62

error_reporting()

error_reporting()で設定した

エラーレベルはデフォルトの エラーハンドラで利用される
自作のエラーハンドラを設定する

ときは、error_reporting()を
考慮して実装しなければならない 実際グローバル変数みたいなもの

Page 63

register_shutdown_function() 設定方法

register_shutdown_function(function(){

if (empty($error = error_get_last())) return;

//

エラーハンドラーで捕捉できないエラーを判定

$fatal = E_ERROR | E_PARSE | E_COMPILE_WARNING

| E_CORE_ERROR|E_CORE_WARNING|E_COMPILE_ERROR;
if (($error['level'] & $fatal_errors) > 0) {

$e = new ErrorException($error['message'], 0,

$error['type'], $error['file'], $error['line']);

my_error_handler($e);

}

});

Page 64

例外のロギング

PHPの標準機能error_log()は、
ちょっと弱い
rollbar, faultlineなどに送る
pixivの場合は例外情報をバックト レースも含めてJSONエンコードし てファイルに追記、Fluentdで収集

Page 65

http://k1low.hatenablog.com/entry/2017/06/13/083000

Page 66

開発時の原則

Page 67

error_reporting()

可能な限り無視しない
: 本番ではDeprecatedを無視
ただしプロジェクト全体にNotice 発生箇所が多い場合は無理しない
一気にリファクタリングしようと してエンバグする方がずっと怖い

Page 68

エラー設定は初期化処理で実行する

error_reportingはphp.iniでも

設定可能だが、PHPで明示的に 設定すれば環境依存がなくなる

深いところで仕方なく変更する

必要に迫られたときは、小さな スコープで変更してすぐに戻す

Page 69

段階的にエラーレベルを上げる

pixivの開発時にもまだNoticeが
発生する箇所が残ってる…

やむを得ず変更する際は浅い位置

で設定する

(例:コントローラ)

Deprecatedはユニットテストで
追い詰めていく

Page 70

pixivの設定

//

全体設定

error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
//
ユニットテスト

error_reporting(E_ALL);

//

新規ファイルや動作確認できたページ

//
(コントローラー)単位でレベルを上げる

setErrorReportingAll();

Page 71

べんりヘルパー関数

/**

*

開発時のみエラーレベルを最大に上げる

*

*

本番(運用環境)には影響しない

*/
function setErrorReportingAll() {

if (is_development()) {

error_reporting(E_ALL);

}

}

Page 72

深いところで一時的にエラーレベルを変更

/** mcrypt */

は7.1で非推奨、7.2で削除予定

function mcrypt_wrapper($text) {

$err = error_reporting();

error_reporting(E_ALL & ~E_DEPRECATED);
try { //

mcryptを使った処理

} finally {

error_reporting($err);
}

return $result;

}

Page 73

手を出せないライブラリ/SDK

/** */
なんかエラー吐きまくる古いSDKのラッパー

function hoge_legacy_wrapper($text) {

$err = error_reporting();

error_reporting(E_ALL &~E_NOTICE & ~E_DEPRECATED);
try {
$result = \Hoge\Legacy\SDK::proc();

} finally {

error_reporting($err);
}

return $result;

}

Page 74

whoops!

Page 75

whoops!

“PHP errors for cool kids”
エラー処理用フレームワーク

set_exception_handler()は
ひとつしかハンドラを登録できな いが、whoopsは複数登録できる

Page 76

Page 77

whoops! ハンドラー

ハンドラー は複数登録できる

(関数)
スタック

(FIFO, 後入れ先出し)

ロギング・デバッグだけでなく エラー画面の表示にも利用できる
WEB+DB Press Vol.96サンプルに

whoopsを使った設定例書きました

http://gihyo.jp/magazine/wdpress/archive/2017/vol96

Page 78

whoops! 注意点

PrettyPageHandlerはべんりだが もし本番環境で有効化されてしま うと脆弱性や情報漏洩の原因にす
らなりかねない

設定に自信がなければ、単に

開発環境用のカッコイイエラー 画面として割り切るのが安全

Page 79

whoopsの有効化

//

開発時のみWhoopsを有効化する場合

if (is_development()) {
$handler = (PHP_SAPI === 'cli')

? new \Whoops\Handler\PlainTextHandler

: new \Whoops\Handler\PrettyPageHandler;
$whoops = new \Whoops\Run;
$whoops->register();

}

Page 80

落穂

Page 81

エラーレベルとビットフラグ

エラー発生時のレベルと

error_reporting()

はビットフラグ

E_ALL

は全種類のエラーの

フラグを立てた状態

E_ALL & ~E_NOTICEのように
反転してビット積をとるのが簡単

Page 82

ビットフラグの確認方法

printf('%015b', E_ALL); // => "111111111111111"
printf('%015b', E_DEPRECATED);
// => "010000000000000"
printf('%015b', E_ALL & ~E_DEPRECATED);
// => "101111111111111"

Page 83

trigger_error()

エラーを意図的に発生させる
ただし全てのエラーを発生させら れるわけではなくE_USER_系のみ 今日エラーはあまり考慮されてな
いので、役立たないかも…
USERの有無は別のエラーなので

Page 84

trigger_error利用例

//

関数をリネームしたいとき

/**
* @deprecated {@see grant()} を使ってください

*/
function credential(array $data){

$msg = 'credential() is obsolete function. Use
grant() instead.';
trigger_error($msg, E_USER_DEPRECATED);
return grant($data);
}

Page 85

もうひとつのエラーハンドリング

set_exception_handler()

を使った

ハンドリングは…実際(説明)難しい

細かなエラーハンドリングは
try-catchを使った方がやりやすい
エラーの仕組み

(このセッションで説明

よりは把握しやすい

した内容!)

https://gist.github.com/zonuexe/577d089815e241d5da26

Page 86

pixivの場合 (AppRunnerパターン)

アプリケーション

(PC版, スマートフォン版,

ごとにAppRunner と

etc...) (社内用語)
呼ばれるクラスを定義する

例外の種類ごとにcatchして、
適切な処理(ロギング・エラー表示)
などをする

http://inside.pixiv.net/entry/2015/12/18/210721

Page 87

pixivの場合 (なぜこのパターンか)

例外にも種類がある
アプリケーションのバグ
クエリパラメータの異常入力

それらのアプリケーションの 事情に沿ってロギングする

Page 88

参考資料

Page 89

参考資料

だいたいPHPマニュアルに載ってる

http://php.net/language.operators.errorcontrol
http://php.net/set_error_handler
http://php.net/set_exception_handler
http://php.net/register_shutdown_function
http://php.net/ErrorException http://php.net/display-errors
whoops!

https://github.com/filp/whoops

Page 90

参考 資料
(にすると良い)

PHPのエラーと例外再入門

by Hiraku NAKANO
https://speakerdeck.com/hirak/php-error-and-

exception

faultline/faultline-phpによるサーバ 管理不要なエラートラッキングとその

効果 by Ken'ichiro Oyama

http://k1low.hatenablog.com/entry/2017/06/13/083000

Page 91

参考資料

(pixiv AppRunner)

(ピクシブ株式会社 Advent Calendar 2015)
健全なpixivは健康なPHPに宿る〜

モダンPHPを保つ7つの鍵

http://inside.pixiv.net/entry/2015/12/18/210721

ソースコード

(サンプル)

https://gist.github.com/zonuexe/577d089815e241d5da26

Page 92

最後に

Page 93

メンバー全員が知るべき?

知っておくとためにはなる…かも でも完全に理解してないといけな
いわけではない
とは言っても社内で誰かが知って
おかないと困ることではある
Noticeを潰せ」だけ周知したい

Page 94

Page 95

Webサービスの開発基盤
を設計したいひとも

Page 96

創作文化のための開発に
興味があるひとも

Page 97

今回の内容がいまいち
ぴんと来なくても大丈夫

Page 98

PHP, JavaScript, Ruby,

Scala, Go, データ分析,
インフラ, その他…

Page 99

まずは一度オフィスに

遊びに来ませんか
ヾ(〃><)ノ゙☆

Page 100

Twitter @tadsan宛

または

recruit.pixiv.net

Page 101

使用フォント

   _
   \ヽ, ,、

セプテンバーM・L

     `''|/ノ       .|  _    |

Migu 1M

 \`ヽ、|    \, V

Rounded M+ 2p

      `L,,_       |ヽ、)
     .|
    /           ,、
    /        ヽYノ    .|       r''ヽ、.|    |        `ー-ヽ|ヮ     |            `|    ヽ,    ,r      .|      ヽ,r'''ヽ!'-‐'''''ヽ、ノ  ,,,..---r'",r, , 、`ヽ、 ヾ
 ヽ、__/ ./ハレハ i`ヽ、 `''r`ミ_
   .レ//r,,,、 レ'レハヾ,  L,,_ `ヽ、     "レ, l;;;l   l;;;l`i.リレ' リ ̄~~
     ヽ、 ワ `"/-'`'`'
       `''''''''"      ┼ヽ  -|r‐、. レ |                  d⌒) ./| _ノ  __ノ