PHP

php初心者へ送るベストプラクティス

はじめに

私がエンジニアになりたての頃に書いた記事を、エンジニアになって数年経つのでチェックして編集しました。
エンジニアを目指し始めの頃からエンジニアになったばかりの頃くらいまでに書いていたメモを元にしているので、今では当たり前になってしまっていることも含めて初心者の頃に知っておきたかったことがまとまっているかと思います。

ベストプラクティス

大前提 phpstormなどのIDEもしくはそれに準ずるものを使う

適切にカスタマイズしたものを使うならともかく、特にサクラエディタや秀丸などのテキストエディタでプログラムを書くのは控える。
自分でカスタマイズする場合も、IDEを基準とする。
具体的にはメソッドの定義元や使用箇所を確認したり、private関数のクラス外からの利用など諸々簡単なエラーチェックはエディタでできていないとつらい。
-> 「IDEを使ってない人もいることを考えろ!」とか言うくらいならエンジニアなのだから自身がカスタマイズで解決する。できないなら素直にIDE使う。

なお、phpの場合はVSCodeよりphpstormの方が圧倒的によい。有料だがphp用で機能が充実していて、VSCodeのようにプラグインをいれまくらなくても最初から機能する。間違いを教えてくれたり矯正してくれるのでエラー解消にかかる時間やスキルアップにかかる時間をかなり節約でき、一年後の状態が確実に違う。

1クラス1ファイル

1つのファイルに複数のクラス定義を書かない。
また、クラス定義のファイルにはクラスのことしかかかない(読み込んだだけで実行するとかは絶対にしない)。

エスケープは出入り口で処理する

PHP->DB であればDBに保存する際にDB用のエスケープのみをかける。
DB内にHTML用のエスケープをした状態で保存したりは絶対にしない(やると後で多重エスケープ他諸々で地獄を見る)。

HTMLのエスケープはテンプレートでもよい。が、フロント側がユーザ入力に基づく値なのかを全て正確に判断するのは困難なので作業者が異なる場合は事前にどちらがやるかを決め、フロントでやる場合はどれがエスケープ必要か伝えておくこと。
混ざると多重エスケープやエスケープ漏れの原因となるので統一されていることが優先。
既にテンプレートでエスケープするようになっているなら異端なことはしない。

global変数は使わない

global変数はカオスの元。
defineもglobal汚染の一種で処理コストも重いので使わない。
代わりにclassのconstで行い、組み合わせなどconstで定義できないものはget~メソッドを作る。
phpのバージョンが8.1以降ならEnumも使える。

エラー制御演算子(@)は書かない

全員が完全に理解しているなら書いてもいいっちゃいいが本来エラー制御演算子は使わずに制御できるはず。
必要なエラーが無視されるという事態が発生したり対策でコーディング規約をごちゃごちゃするくらいなら最初からエラー制御演算子を利用禁止する方がいい。

$_POST, $_GETなどについて

基本的に$_POST, $_GETは使わず、filter_input()メソッドを使う( $_POST['hoge'] なら代わりに filter_input(INPUT_POST, "hoge") )。
それもコントローラで使うのみでその他のクラス内では使わない(クラスやメソッドには変数を渡す)。
具体的には、実行するファイルに記載してクラス内には$_*やfilter_inputを書かない。
ただし、検証・整形のためのクラスであれば$_*系のデータをまるっと渡す前提の設計はあり。

phpDOC

・クラスにはPHPDOCを必ず書く
何のクラスか、の説明をしっかりと記載し、クラス名と説明と処理がかけ離れないようにする
・メソッドも基本的にはDOCを書くが、getterなどのようにメソッド名ぱっと見で処理がわかるものであればその限りではない
・非推奨になったものは @deprecated を記載してなぜ使わないのか、使わずにどうすればいいのかを書く 適宜@see も使う

継承について

・基本は「迷うなら継承しない」
-> "継承する方がベターなケース"で継承しなくてもコスト増加は少ないが、"継承すべきでないもの"を継承したときのコスト増加は大きい
あるメソッドがあったとして、継承していなければそのクラスのそのメソッドの利用箇所だけチェックすればよいが、基底クラスを変更すると継承先での同メソッド利用箇所も全て検証が必要。継承の継承の継承の...となるとどの段階でオーバーライドしているかも確認しなければいけないので更に大変。
また、継承するということは基本的に同様の動作を期待しているはずなので
・親クラスにないpublicな新メソッドはできるだけいれたくない(禁止ではないが)
-> 特に、簡単に似た名前の別メソッドをつくったりしない
・オーバーライド時は継承元と同じ仕様
コンストラクタもメソッドも継承するからには受け取る引数や返り値の数や型はすべてあわせる
(false返したりnull返したりバラバラだとつらい)
-> それができないなら継承ではなくクラス内で利用する方がベター

参照渡しをしない

可読性とかメモリとか色々理由あるけどPHPでは参照渡しをしないほうがいい
理由のうちの一つ(参考): http://tanakahisateru.hatenablog.jp/entry/2013/12/12/012728

配列で返す値が0件のときはnullではなく空配列で返す

他と同様nullにしたくなるが、そうするとcount()が効かなかったりforeach()ができなかったりと配列操作が効かなくなってしまう
nullは適切に利用する必要があるが、配列の0件はnullではなく空配列だということを覚えておくとよい

その他注意点/小技

in_array()は第三引数trueを指定する
https://qiita.com/tadsan/items/2a4c3e6b0b74a408c038 "apple"==0 true がひどい
http://php.net/manual/ja/language.types.string.php#language.types.string.conversion <- 要はこれが曲者
・ファイルの最後を?>で閉じない
その後にスペースなどあれば出力され、headerが動作しなかったりする
どうしても閉じるならその前にexit;をおく
・コントローラ以外でリダイレクトしない
エンドポイントとなるphp(コントローラ)以外で極力しない。メソッド実行して戻り値が帰ってこず、勝手にリダイレクトされるとデバッグしづらい。ただし、php8.1以降であれば返り値にneverを明示するという手段がある。
・コールバックは使わない
Javaなど他の言語から来た人で使う人が偶にいるが、phpでコールバックを使う方がメリットのあるケースはない気がする
IDEがメソッド利用箇所を探せなくなったり地味なデメリットがありバグがでやすくなる
・テンプレートに数字を直接かかない
そうすることでプログラムの変更があってもテンプレートの編集漏れがなくなる
数字ということはバージョンアップやロジック変更で変更になる可能性が高い箇所
正確には"変更の可能性がある部分をテンプレートに直書きしない"だがひとまずは数字を書かないと考えておく方が漏れにくい
テンプレートに数字を書かざるをえないケースもないとは言わないが基本は定数化して変数としてテンプレートに渡す
・仮のものはtodoなどつけてコメントかいておく

おわりに

phpはなるべくエラーを出さないで処理を通すようになっている。
そのため簡単に作るにはいいがエラー処理などをきっちりやるにはかえって不便なところもある。
しっかりとしたものを作るにはきちんと特性を把握して対応することが必要となってくる。

-PHP