PHP開発者が心得ておくべき10の鉄則

  • 261
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。
自ブログ(Monaural Sound)より転載

 PHPだけに限った話ではないが、プログラミングを行うに当たって、心得て、準備しておくと失敗が劇的に減る事柄をまとめてある良い記事を見つけたので、私なりの解釈で意訳しつつ、経験則を踏まえてあらためてまとめてみた。

オリジナル記事: Top 10 PHP Tips for Developers

 どれも基礎的なことで、長くプログラムやっていると「何を今更…」的な事も多いのだが、逆にプログラミングに応用が利くようになってくるとおざなりにしがちな点もある。

1. OOPで行こう!

 OOPとはオブジェクト指向プログラミング(Object Oriented Programming)のことで、原文では「開発する際はオブジェクト指向プログラミングを活用しないと不利になる」と云っている。
 オブジェクト指向型プログラミングの説明をすると長くなってしまうので、ここでは詳しくは触れないが、簡潔に言えば、OOPは「工場で流れ作業的にモノを作る」のに近くて、一方で対となる手続き型(構造型)プログラミングは「職人が独自の作法でモノを作る」と例えられるかもしれない。

 どういう事かというと、例えばOOP型の場合、工場で車を作る流れ作業を考えてみるとわかりやすいかもしれない。
 ボディーを作る部門とエンジンを作る部門、ホイールやサスペンションなど各パーツを作る部門があり、それぞれ作ったパーツを寄せ集めて、最終的にパーツを組み立てる部門もあって、最終的に車というモノを完成させることになる。
 この場合、各部門がPHPで言うところのクラスであり、各部品製造クラスが車を組み立てるベルトコンベア・ライン(基底クラス)の子クラスになっているという建て付けだ。そして、各クラスにはエンジンならエンジンを完成させるための手順(メソッド)があり、エンジンの部品を作るための子クラスがあり…というように、クラスやメソッドは分業化されているイメージだ。そして、もちろん、最終的に出来上がる車がクラスのインスタンスとなる。
 各製造部門(クラス)の手順(メソッド)を派生(オーバーライド)したり、与える材料(メンバー変数など)を変更したりすることで、作る車種や搭載機能を低いコストで変更できるような汎用性が強みでもある。

 方や手続き型(構造型)の職人が焼き物を作る場合では、土作り→土練り→ろくろ成形→乾燥→素焼き→絵付け→本焼き…という固定手順の中で、材料である「土」を完成品である「焼き物」という製品に変質させていくことになる。
 つまり、グローバル変数に格納された「土」を「焼き物」に変化させるため、各手順(関数)にその時点の「焼き物」の状態(グローバル変数)をひき回していくというイメージだ。この固定ルーチン型でも、各関数に細やかな変更を加えることで完成形である「焼き物」の種別や絵柄等を変えられるが、「茶碗」と「皿」と「壺」にそれぞれ3種類の絵柄を…という注文に対応するためには各手順(関数)の処理が複雑化するであろうことは容易に想像できる。
 また、PHPの処理として考えた場合、「素焼き」や「本焼き」の関数は共通化できる部分が多く、コードとしては非効率である。焼成作業での共通手順となる「窯詰め」や「窯出し」は共通化してしまいたいが、本焼きでしか発生しない「上絵付け」や「上絵焼成」の分岐手順は関数内に入れ込まないといけない…というジレンマも出て来る。
 とはいえ、この例のように伝統工芸のルーチンであれば、確立されている手順が将来的に変更するされる必然性も少ないため、逆に手続き型でプロセスを構築してしまった方が良いということもある。

 両者それぞれにメリット・デメリットはあるのだが、環境やニーズの移り変わりが激しい昨今のWEBにおいては、拡張性・汎用性が高いオブジェクト指向型プログラミングの方がメリットが非常に多いというのは周知の事実である。

2. require_once()include_once()はハイコストである

 原文を意訳すると、require_once()include_once()といった「_once()で終わる関数から離れよ」というタイトルで、下記のような事が書いてある。

私たちはみんな、include()が失敗した場合に単なる警告を与えることと、require()が失敗したときに致命的なエラーでスクリプトが停止することを知っている。
忘れてはいけないことは、include_once()require_once()は、サーバリソース上で非常にコストが高いということだ。我々にはそれについてできることは何もなく、それはPHPの仕様なのだ。
ただ、これらの関数がサーバーのリソースを大きく消費することを覚えておかねばならない、特に、巨大なフレームワークを利用する時や、あなたがコード設計をする場合にはなおさらだ。

 一理あるのだが、そのまま鵜呑みにしてはいけないポイントでもある。というのも、確かにrequire()require_once()include()include_once()についてはコストが高い関数であるのだが、かといってすべからく_once()なしの関数にシフトしたからと言ってリスクヘッジができるわけではないからだ。
 むしろrequire()に関しては、require_once()を使った方がサーバリソースの節約につながることが多い。このTIPSについて私なりに言い換えるとすれば、

requireincludeの使い分けを理解し、リソースパスはサーバの絶対パスで指定すること

 ──がベストプラクティスではないだろうか。リソースパスに相対パスを指定してしまうと、外部ファイルの読み込み時に「検索」という処理が行われてしまう(php.ini設定のinclude_pathに依存した検索を行う)ために、サーバリソースを無駄に使ってしまう。絶対パスを指定することで「検索」という処理を省くことができる。
 もし、環境によるサーバパス(ドキュメントルート)の違いを吸収して絶対パスのハードコーディングを回避したい場合なら、

require_once( $_SERVER['DOCUMENT_ROOT'] . '/includes/extend.php' );

 ──とスーパーグローバル変数の$_SERVERを参照することでサーバパスの環境依存にも対応できる。

 また、require()include()の使い分けについては、基本的に出力用のテンプレート等を読み込む場合はinclude()を、PHPの拡張処理を読み込む場合はrequire_once()を使うという住み分けで覚えておくと混乱しない。詳しくは、下記の記事がわかりやすいので、一読をおすすめする。

差し込みたければ include、利用したければ require_once

3. 開発時はエラーレポートをONに

 PHPでの開発プロジェクトを開始する時に一番最初にすべきことは、すべてのエラー報告を有効にするためにerror_reporting()E_ALLを指定することである。おそらくこの世にエラーのないプログラムを一発でコーディングできる人間は存在しないと思う。自分を人間だと思っているならば、開発時にはエラーレポーティングはONにしよう(笑)
 なお、一方でエラーメッセージの出力がハイコストであることも覚えておく必要がある。プロジェクトの公開環境ではE_ALLやE_STRICTが有効化されていても、エラーの出力が行われないように、display_errorsをOFFにしておく。
 私の考える最善のエラー設定は、下記の通りだ。

開発環境や開発時

 基本的にphp.iniに指定しておくのが一番簡単。エラーログも出力しておくと、Ajax処理やバッファリング中のエラーも拾いやすくなるのでおススメ。なお、エラーログ出力用のファイルは事前に作って、パーミッションや所有者を最適化しておく必要があるので注意。

error_reporting = E_ALL | E_STRICT
display_errors = On
log_errors = On
error_log = "/var/log/php_errors.log"

 もしくは、プロジェクトルート毎に.htaccessで指定してもよい。これだと複数プロジェクトをホスト毎に走らせられたりして効率が良い。ただし、E_ALLのビットマスク値がPHPのバージョンで異なるので注意(PHP < 5.2 = 2047、PHP < 5.3 = 6143、 PHP < 5.4 = 30719、 PHP <= 5.5 = 32767)

php_value error_reporting 32767
php_flag display_errors On
php_flag log_errors On
php_value error_log "/var/log/php_errors.log"

 以下はあまりおススメできないが、設定値を柔軟に制御したい場合などはPHPソース内に指定する。

ini_set( 'display_errors', 1 );
ini_set( 'log_errors', 'On' );
ini_set( 'error_log', '/var/log/php_errors.log' );
error_reporting( -1 );

公開環境や運用時

 こちらも基本的にphp.iniに指定しておくのが一番無難。サービスの脆弱性が露出するので公開環境ではエラーは必ず出力しないようにすること。なので、エラーログへの出力が超重要になる。

error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off
log_errors = On
error_log = "/var/log/php_errors.log"

 もしくは、.htaccessでの指定。E_ALLのビット値がPHPのバージョンで異なるので注意(下記の例はPHP5.4.x以上でE_DEPRECATEDとE_USER_DEPRECATED以外のE_ALLをログ出力する)

php_value error_reporting 8191
php_flag display_errors Off
php_flag log_errors On
php_value error_log "/var/log/php_errors.log"

 公開環境でエラー設定を制御しなきゃいけないケースは避けたいので、以下のようにPHPソース内への指定は甚だよろしくない。

ini_set( 'display_errors', 0 );
ini_set( 'log_errors', 'On' );
ini_set( 'error_log', '/var/log/php_error.log' );
error_reporting( -1 );

 なお、公開環境ではログの肥大化によるリソース圧迫やサービスパフォーマンスの低下を防ぐためにもエラーログのログローテーションは考慮しておく必要がある。
 PHPエラーログのログローテについては、下記が参照になるかと。

PHP エラーログでローテーションを!

4. 必要に応じてフレームワークを利用する

 PHPの生みの親でもあるラスマス・ラードフ氏は「フレームワークは通常のPHPコードより遅いから、フレームワークは使わない方が良い」と云っているが、それはフレームワークに対しての一面的な見解でしかないことを知っておく必要がある。私も個人的にフレームワークは嫌いなのだが、一方でその有用性は認めざるを得ない。
 では、フレームワークの有用性とはなんだろうか。

  • 圧倒的な生産性を得るためにパフォーマンスの犠牲はやむを得ない
  • 不正なコードを最大限除外することによって得られるプロジェクトの信頼性

 フレームワークを利用する価値はこの二点に尽きるのではないだろうか…と私は思っている。
 そして、フレームワークの弊害はPHPというプログラム言語自体を抽象化してしまう側面を持っていることだ。フレームワークに傾倒し過ぎると、PHPが本来持っているパフォーマンスや拡張性を見失い、極論すると狭いフレームの制限下に捕らわれたサービスしか生み出せないPHP技術者になってしまうこともありえるのだ。

では、どんなPHPフレームワークを選べばいいのだろうか?

 PHPのフレームワークは非常に多い。「CakePHP」「zend framework」「Symfony」「FuelPHP」「laravel」「codeigniter」「Slim」「Phalcon」…数えれば切がない。
 日本ではPHPフレームワークとしては「CakePHP」が主流で、方や世界では一昔前は「codeigniter」が全盛だったが、最近は「laravel」にトレンドが移って来ている。パフォーマンス面では「Phalcon」が最速で、「Slim」「codeigniter」がそれに続いている状況だ。

 そんな状況の中で、アンチ・フレームワーク派な私が一押ししたいのが「codeigniter」だ。「codeigniter」の良いところは学習コストが低いこと、そして非常に導入が楽なところだ。他のリッチなフレームワークと較べると機能面で劣る部分があるものの、メリットのアドバンテージはとても大きいと思っている。

 私は、フレームワークの一番の難点はPHPというプログラム言語以外に独自の「ルール」を学ばなければならないという学習コストが高い点だと思っているので、そのコストが低く、導入敷居も低い「codeigniter」は現時点でどのフレームワークより魅力的である。ただ、「codeigniter」は大規模システムには向かないと云われているので、選定するプロジェクトの性質を見極めてからチョイスする必要がある。

 結局のところ、フレームワークはPHPという大きな枠を超えられない開発支援モジュールであることを理解したうえで、まず学ぶべきはPHP本体であり、そのうえでプロジェクトの性質や納期、開発体制などをかんがみてチョイスしなければならないのだろう。

5. PHPの組込み関数を使い倒せ

 PHPを使って開発を行う以上、ある程度の組込み関数やメソッドを覚えて、そらで使えるようにしておくことが非常に重要だ。もちろん、その前に構文としてのifforeachなどは習得しておかねばならない。そのうえでプロジェクトに必要となる独自機能を関数やメソッドを組み合わせて作っていくのがセオリーとなる。

 では、どの程度の関数を覚えておけば良いのだろうか…?

 2016年1月現在、PHPには標準で4,598個の組込み関数と5,022個のビルトインメソッドがある。このすべてを覚えていて、そらで使える人はおそらくいないのではないだろうか。

 私も実際のところ、そらで使えるPHPの標準関数は300個前後である。だが、WEBの開発であればこれぐらい覚えていればほぼ事足りる。まれに毛色の違う事をしたくなったら、PHPのドキュメントを調べれば良い。
 まぁ、それでも数は多いので、あくまで私の経験則だが…まずは、配列操作系と文字列操作系、ファイル操作系、マルチバイト文字列関連、各種出力系の関数はある程度覚えておくとコード生産性が高くなるだろう。そのうえで、バッファリングや数学関数、セッション、データベース、サーバコマンド関連あたりを覚えればWEBアプリ開発のシーンであればかなり自由が利くようになると思う。

 そして、一番大事なのは独自の処理を書く前に、「やりたいことが組込み関数でできないか」を調べることだ。PHPでは大抵の処理は組込み関数で対処できるように準備されている。自前で作った関数よりも組込み関数の方が生産性でもパフォーマンスでも圧倒的に性能が良いことを覚えておくべきである。

6. データベースを守るのはプログラマーの務め

 原文ではmysql_real_escape_string()を利用してデータベースにデータを格納する前に適切な無害化を行う必要があると書いてある。確かにデータベース処理の無害化はプログラマーの義務であるのだが、推奨されているライブラリには可用性がない。
 というのも、mysql_real_escape_string()を含む「mysql」モジュールはPHP 5.5で非推奨となっていて、PHP 7では削除されているからだ。PHP公式ではMySQLデータベースの処理はMySQLiモジュールかPDO_MySQLモジュールを使いなさいと謳っている。
 まぁ、どのモジュールを使おうが、処理としてのSQLエスケーププロセスをデータベース入出力に組み込むことには変わりないので、MySQLiを利用するなら、mysqli_real_escape_string()を、PDO_MySQLを利用するならPDO::quote()を利用すれば良い。

 ただ、私的にはデータベースのSQLエスケープには前述の「手動エスケープ」ではなく、「プリペアドステートメントへのプレースホルダ代入」を使用することをおススメしたい。特にPOSTやGETといったユーザ入力値を直接データベースアクセスに利用する場合、プレースホルダ型であれば、エスケープ処理が自動で行われるため、プログラマーによるエスケープ忘れによるSQLインジェクション等のシステム脆弱性が発症するリスクをかなり抑えられるからだ。

$var = $_POST['user'];
$db = new PDO( 'mysql:host=localhost;dbname=database;', 'root', '' );
$sth = $db->prepare( "SELECT * FROM `users` WHERE user=:var" );
$sth->bindParam( ":var", $var );
$sth->execute();

 上記の例はPDOプリペアドステートメントを利用したデータベースアクセスの例である。ユーザ入力のパラメータを手動でクォートする必要がないので、プログラマーがパラメータのエスケープ処理を意識していなくてもSQLインジェクションから保護される。

7. GETは使うな、POSTを使え

 原文はGETメソッドによるパラメータ渡しはXSS(クロスサイトスクリプティング)等のシステム脆弱性を生む温床になりえるので、POSTを使いなさいと云っている。まぁ、確かにその通りではあるのだが、これも極論過ぎるきらいがある。

 基本的に、システムへのパラメータの受け渡しはPOSTで行うべきであり、どちらかのメソッド固定でないと成り立たないようなアプリケーションはちょっと問題があるといえる。しかし、アプリケーションの利便性や生産性といった側面から評価をする場合、パラメータのGET渡しが必ずしも悪ではないことも覚えておくべきである。

 特にWebAPI型のアプリケーション・プロジェクトでは利用者からのURLベースでの簡便なアクセスがサービスの根幹たりえるため、入力値にGETパラメータを期待することは間違っていない。しかし、そういう場合においては、意図しない、むしろ悪意のあるパラメータが入力されることも予測してパラメータの無害化を行っておくことが必要になってくる。

8. コードを書く前に「描け」

 原文を意訳すると「コーディングに入る前にシステム設計をしなさい」というTIPSだ。当たり前のように思えるが、特に一人で開発をしているとこの部分を端折ってしまうことが多いのも事実だ。
 経験則的に、殴り書きでもいいので作ろうとしている機能のフローチャートを書いてからコーディングした時と、コーディングしながらフローを考えていった場合とを較べると、確実に前者の方が開発工期が短いのだ。

 私もそうなのだが、開発者はコードを書いている時に幸せを感じることもあって、往々にして頭に浮かんだ処理をすぐに書いてみたくなる。しかし、そこをグッと我慢して処理全体の流れを設計してからコーディングに入った方が開発時間は抑えられ、バグも少なくなるのである。

 一方、複数人で開発するようなプロジェクトであれば、必ずプログラマーたちにはシステム設計をさせて、設計レビューを行った後にコーディングを開始するようにマネージメント側がルーチン・ワークを定義してあげると生産性が向上するはずである。

9. プロジェクトを理解せよ

 原文は「画家が見たこともないモノを描くことができないように、歌手が聞いたこともない曲を歌えないように、プログラマーは完全に理解できていないプロジェクトをコーディングできないものだ。だから、プロジェクトの全貌を理解しなさい」と謳っている。正論であり、異論の挟む余地は少ない。

 経験則的に、自分が全体を理解していないサービスにおいて、この部分だけを作ってくれればいいから…とか言われて作ったものは、結果的にシステム的な一貫性が失われていたりして保守性が低くなり、運用時に問題が出たりすることが多かった。
 とはいえ、現場の技術者に、参加しているプロジェクトの全貌が常に開示されるわけではないのも現実だ。まぁ、そんな場合でも「何(誰)のために」「何をする」システム(機能)なのかだけでもきちんと理解してからコーディングに入るべきであり、それら「要求仕様」が明確でないシステムについては前もってそのリスクを埋める努力をする必要はあるだろう。

10. 書いて、書いて、書きまくれ!

 つまるところ、プログラミングのスキルを向上させるには「書いて書いて書きまくる」しかない…というのは万国共通の認識のようだ。

 とにかく「書いて・動かして・失敗して」を繰り返せば、繰り返した数に比例してコーディング能力は上達して行くわけだ。まぁ、これは何もプログラム言語に限った話ではなく、外国語の習得とかも一緒なのだろうと思う。ただ、特にプログラム言語は覚えなきゃいけない構文や単語(命令)が多くても数百~数千程度と実際の言語より少ないので、習得はしやすいと云えなくもない。

 また、スクリプト系の高級言語の場合、どれか一つでもプラグラム言語覚えてしまうと、他の言語でも何とか読めるし、見様見真似で書けるようにもなるという利点もある。

 原文でも述べられているが、プログラム言語の解説本や先輩のコードをいくら読んでもプログラミング・スキルはさっぱり伸びない。ただひたすら書かない限り上達しないということだけは肝に命じておいてほしい。

まとめ

 最後にPHPの生みの親であるラスマス・ラードフ氏の面白い言葉を引用しておこうかと。

PHPはひどいコードを動かすのが驚くほど得意な寛容な言語である

 氏曰く、「世界最悪のコードを書いたとしても、PHPならまだ動くし、実際速度も出るし、スケーリングも可能」という、PHPの寛容さに甘えて(笑)、ぜひとも2016年はPHPを書きまくりたいものである。