PHPプログラミング診断室

第5回 永遠のPHP 4(診断編)

この記事を読むのに必要な時間:およそ 5.5 分

診断

1. PHP 4らしいコード

まずコードを見た印象としては,実にPHP 4らしいコードだな,ということを感じました。

ざっと見渡しただけでも,require_once文による外部PHPファイルの読み込み,メンバ変数のvarキーワードによる定義,クラス名と同名のコンストラクタ,メソッドのアクセス修飾子がないなどの特徴があります。また,引数の参照渡しやstatic指定なしのクラスメソッドなどもそうですね。

これらは,PHP 4では問題のない書き方だったのですが,時を経て,変化したPHP 5にはそぐわない書き方になりました。これは言語の変化だけではなく,開発手法や利用するライブラリ,フレームワークが変化したというのもその一因です。こうした環境の変化に合わせて,コードの書き方を変えていくことで,PHPやその周辺ツールの良さをより享受することができます(PHP 5の中でも,5.3未満とそれ以降でまた異なるのですが,それはまた別の機会に⁠⁠。

では,それぞれの個所について見てみましょう。

1-1. require_once文による外部ファイルの読み込み

両コードとも,ファイルの先頭でrequire_once文で外部のクラスファイルを読み込んでいます。当時,複数のクラスファイルで構成されるアプリケーションでは定石だったこの方法ですが,オートローダが普及した今では,オートローダ自身や設定ファイルなど最低限のファイルのみ自分で読み込んで,あとはオートローダに自動で読み込ませるという手法を取ることが多くなりました。

require_once文は悪くない方法なのですが,ファイル位置を相対パスで指定したり,include_pathを別途指定する必要があるなど,少し煩わしさがあります。オートローダを使えば物理的なファイルパスを気にすることなくクラスファイルを読み込むことができるので便利です。

オートローダは自作することもできますが,Composer※1のオートローダを利用するのが簡単です。Composerのオートローダを利用することで,ほかのライブラリやフレームワークを利用する際も同じオートローダを使うことができ,連携しやすい構成となります。

※1
Composerは,PHPで依存管理を行うツールです。アプリケーションが利用するライブラリやフレームワークを管理する手段としてデファクトスタンダードとなっています。https://getcomposer.org/
1-2. 名前空間

Composerによるオートローダを導入して,ほかのライブラリやフレームワークと組み合わせる際に重要になるのが,名前空間の設定です。複数のソフトウェアを組み合わせると,同じ名前のクラスがそれぞれに存在して,競合してしまう場合があります。これはソフトウェア同士だけではなく,開発した時点ではオリジナルなクラス名でも,同名のクラスがPHP言語に導入されてしまう場合もあります(最近では,PHP 5.4からSessionHandlerクラスが導入されて,アプリケーションに存在するクラスと競合するということがありました⁠⁠。

名前空間を導入しておくと,こうした名前の競合を避けることができます。たとえば,下記のように同じUserクラスでも,名前空間をAppFooLibのように別にしておけば,名前空間を含めた完全なクラス名は\App\UserFooLib\Userとなり,同居することできます。

namespace App;

class User {}
namespace FooLib;

class User {}
1-3. クラス名と同名のコンストラクタ

コンストラクタのメソッド名がクラス名と同じ名前になっています。

PHP 4のコンストラクタはこのように記述するしかなかったのですが,PHP 5では,__constructというメソッド名がコンストラクタとして記述できるようになりました。従来の同名コンストラクタも利用できるのですが,PHP 5.3.3以降,名前空間が設定されたPHPファイルでは同名コンストラクタが呼ばれなくなっているので,__constructを使いましょう。

1-4. オブジェクトの参照渡し

PHP 4らしい書き方と言えば,このオブジェクトの参照渡しです。たとえば,Loginクラスのauth()メソッドの引数が参照渡しとなっています。

  function auth(&$userModel) {

また,インスタンスを変数に代入する場合も下記のように参照で受け取るように書かれています。

    $db =& Database::getInstance();

PHP 4では,変数の代入やメソッド呼び出しで引数を渡す際,デフォルトは値渡しとなっていました。そのため,オブジェクトを代入する際は処理速度やメモリ効率を考慮して,参照渡しを行うことがTipsとして知られていました。しかし,PHP 5からは,オブジェクトの実体ではなく,参照のみが渡されるようになったため,パフォーマンスを考慮した参照渡しを行う必要がなくなりました。

また,Loginクラスのauth()メソッドの場合,引数で受け取ったUserModelクラスのオブジェクトに対してセッターメソッドで値をセットしています。このオブジェクトの変更を呼び出し元でも利用するため,参照渡しで引数を渡しています。これもPHP 5であれば,前途のとおりオブジェクトの参照を渡すので,メソッド内のオブジェクトと呼び出し元のオブジェクトは同一のオブジェクトとなります。よって,この理由による参照渡しを行う必要もありません。

このようにPHP 5でオブジェクトを代入する際、&を使った明示的な参照渡しは必要ありません。特にパフォーマンスについては,参照渡しを行うことでかえってパフォーマンスが落ちるケースもあるので,&による参照渡しは基本的には使わないほうがよいです。

1-5. メソッドの重複

LoginControllerクラスを見てみると,checkLoginId()というメソッドが2つ定義されています。PHP 5では,1つのクラスに同じ名前のメソッドは定義できないのですが,PHP 4ではこういった実装が認められていました。同じ名前のメソッドを2つ書くメリットは特にないと思うので,おそらくコピペしてそのまま放置されたのでしょう。

1-6. get_class()関数の挙動の違い

Loginクラスのauth()メソッドでは,処理の先頭で引数のクラスをチェックしています。get_class()関数でクラス名を取得して,期待すべきクラスでなければ,returnするようになっています。

PHP 4では,このget_class()関数の戻り値はクラス名を小文字にした文字列でした。よって,このコードでは期待すべきクラス名UserModelを小文字にしたusermodelと比較しています。しかし,PHP 5ではクラス名がそのまま返ってくるので,戻り値はUserModelとなります。つまり,このままこのコードをPHP 5で実行すると,$userModel変数がUserModelクラスのオブジェクトでも,この式はtrueになりません。

PHP 5で動かすなら,比較文字列をUserModelにするか,get_class()の戻り値をstrtolower()関数で小文字に変換する必要があります。

単に引数の型をチェックしたいということであれば,PHP 5のタイプヒンティングを使う方法もあります(下記⁠⁠。こちらのほうが意図がわかりやすいコードになりますね。

  function auth(UserModel $userModel) {
1-7. ereg()は非推奨

LoginControllerクラスのcheckPassword()メソッドで使われているereg()関数は,PHP 5では非推奨(E_DEPRECATED)となっています。これはpreg_match()関数で代用しましょう。

  ereg('^[a-zA-Z0-9]+$', $this->values[PASSWORD])
  
  preg_match('/^[a-zA-Z0-9]+$/', $this->values[PASSWORD])
1-8. クラスメソッドにstaticが付いていない

Loginクラスのauth()メソッドは,LoginControllerクラスでの呼び出し方を見るとクラスメソッドを想定しているようです。

        if (Login::auth($userModel)) {

このようにクラスメソッドとして呼び出しているメソッドの定義にstaticキーワードが付いていない場合,Strictエラー(Non-static method Foo::hello() should not be called statically)が発生します。

コードを読む際もメソッドがインスタンスメソッドなのかクラスメソッドなのかの判別ができないので,クラスメソッドの定義にはstaticキーワードを付けておきしょう。

2. addslashes()によるエスケープ処理

PHP 4固有のことではないのですが,気になる個所があります。

Loginクラスのauth()メソッドでは,データベースへの問い合わせが実行されているのですが,検索条件を組み立てている個所で値のエスケープにaddslashes()関数が使われています。addslashes()関数はマルチバイト文字などを考慮せずに機械的にエスケープを行ってしまうため,利用する文字エンコーディングによっては,文字列が壊れてしまったり,本来は無効化すべき特殊文字をエスケープできない場合があります。

SQLに含める値をエスケープするにはaddslashes()関数ではなく,データベースドライバに定義されている専用のエスケープ関数(PostgreSQLならpg_escape_string()/pg_escape_literal(),MySQLならmysqli_real_escape_string()など)を用いるほうが安全です。また,もし可能なのであれば,PDO(PHP Data Object)を使い,プリペアドステートメントで値をバインドする方式に変えるほうがよりよいでしょう。

addslashes()関数を使う場面はおおよそないとも言えるので,この関数が登場した場合は要注意です。

3. 連続したセッターメソッド

これは何かマズイことがあるというわけではないのですが,実装について気になった個所です。

Loginクラスのauth()メソッドでは,$userModel変数に対してセッターメソッドが15行ほど並んでいます。各セッターメソッドに対して連想配列$recordの対応するキーの値をひたすら渡しています。まあ丁寧でよいのですが,冗長なので,下記のように連想配列ごと引数で渡して,UserModelクラス内で値を一括でセットしてくれるようなヘルパーメソッドが欲しくなるところです。

// 連想配列の値を属性にセットする
$userModel->setAttributes($record);

今回は,主にPHP 4の書き方に注目して治療するポイントを見てきました。このポイントをベースに,次回はPHP 5らしい書き方に治療していきます。お楽しみに。

著者プロフィール

新原雅司(しんばらまさし)

1×1株式会社 代表取締役

大阪でPHPを駆使してWebシステムの開発を行う日々。MotoGPをこよなく愛す。愛車はPCX。

Twitter:shin1x1
blog:Shin x blog