PHPプログラミング診断室

第4回郷に入っては郷に従え(治療編)

修正コード

第3回目で診断したPHPコードについて、問題点を修正したコードを書いてみましょう。

修正コードのポイントですが、まずコードの書き方については診断編で列挙した個所を修正しています。さらに設計についても見直しを行い、独自で実装しているセッション機能を、PHP標準のセッション機能を使うよう変更しています。最後に今どきのPHPの書き方ということで、名前空間やクラス定数などを盛り込んでいます。

では、治療後の修正コードをどうぞ。

<?php
namespace Acme;

/**
 * Cartクラス。買い物カゴを定義する。カート情報はセッションに保存する。
 *
 * @author Foo
 */
class Cart
{
    /**
     * セッションキー
     */
    const SESSION_KEY = 'SESSION_CART';

    /**
     * アイテム情報
     *
     * @var array
     */
    protected $items = [];

    /**
     * コンストラクタ
     */
    public function __construct()
    {
        if (session_status() != PHP_SESSION_ACTIVE) {
            session_start();
        }

        if (empty($_SESSION[static::SESSION_KEY])) {
            $this->items = [];
        } else {
            $this->items = $_SESSION[static::SESSION_KEY];
        }
    }

    /**
     * 商品を追加する
     *
     * @param string $itemCode
     * @param integer $itemCount
     */
    public function addItem($itemCode, $itemCount)
    {
        if (empty($this->items[$itemCode])) {
            $this->items[$itemCode] = $itemCount;
        } else {
            $this->items[$itemCode] += $itemCount;
        }

        $this->save();
    }

    /**
     * 商品個数を修正する
     *
     * @param string $itemCode
     * @param integer $itemCount
     * @throws \Acme\NotFoundException
     */
    public function changeItem($itemCode, $itemCount)
    {
        if (empty($this->items[$itemCode])) {
            throw new NotFoundException();
        }

        if ($itemCount > 0) {
            $this->items[$itemCode] = $itemCount;
        } else {
            unset($this->items[$itemCode]);
        }

        $this->save();
    }

    /**
     * カートをセッションに保存
     */
    protected function save()
    {
        $_SESSION[static::SESSION_KEY] = $this->items;
    }

    /**
     * カートを削除する
     */
    public function clear()
    {
        $this->items = [];
        $this->save();
    }

    /**
     * 商品を削除する
     *
     * @param string $itemCode
     */
    public function deleteItem($itemCode)
    {
        unset($this->items[$itemCode]);
        $this->save();
    }

    /**
     * 商品情報を取得
     *
     * @return array
     */
    public function getItems()
    {
        return $this->items;
    }

    /**
     * 合計個数を取得
     *
     * @return integer
     */
    public function getTotalCount()
    {
        $total = 0;

        foreach($this->items as $v) {
            $total += $v;
        }

        return $total;
    }
}

/**
 * NotFoundException
 */
class NotFoundException extends \Exception {}

治療ポイント1. コメントをPHPDocに変更

コメントを記号で彩られた元の形からPHPDocに変更しました。これによりグッと今っぽいPHPコードになったのではないでしょうか。コメントは動作には影響しないものですが、コードを読むという意味ではとても大きな役割を果たします。

PHPDoc形式で書かれたコメント部分をDocBlockと言います。下記がaddItem()メソッドのDocBlockとなります。DocBlockにある@paramタグはメソッドの引数を表しており、addItem()メソッドには、string型の$itemCodeとinteger型の$countの2つの引数があることがわかります。また、戻り値を表す@returnタグがないので、このメソッドには戻り値がないことがわかります(戻り値がない場合は、@return voidと表記する場合もあります⁠⁠。

    /**
     * 商品を追加する
     *
     * @param string $itemCode
     * @param integer $count
     */
    public function addItem($itemCode, $count)

phpDocumentorというAPIリファレンスを自動生成するツールでこのPHPファイルのリファレンスを生成した例が下記です。

phpDocumentorで出力した例
phpDocumentorで出力した例

先ほどのaddItem()メソッドの個所を抜粋しています。DocBlockに記述したコメントやタグが見やすい形式で出力されています。このようにPHPDoc形式でコメントを書くことでこういったツール群の恩恵を受けることができます。

治療ポイント2. ハンガリアン記法からの脱却、メソッド名の見直し

変数名を修正して、ハンガリアン記法から意味のわかりやすい英単語の組み合わせにしました。$a_Item はアイテムが複数であることを示すように$itemsへ、$cGoodsCodeは商品を示す単語をitemに統一して$itemCodeへ、$iGoodsNumも同様に$itemCountへ変更しています。

無駄なプレフィックスがなくなったことで、変数名だけを見ても何を意味するかが明確になり、理解しやすくなっています。前項のPHPDocによるコメントを付けたことで、引数やメンバ変数のデータ型はコメントとして記述しています。

メソッド名についても読みやすいように変更を行いました。いくつかのメソッドではdo_add_item()のように先頭に「do_」が付いていたのですが、これは不要なので削除しています。またPSR-1[1]に従い、アンダースコア区切りではなく、camelBack記法に変更したので、addItem()としています。

治療ポイント3. 配列の操作を修正

配列を格納する変数の初期化では、下記のように[]を代入するようにしました。PHPDocにより、この変数に格納するのは配列であることが明示されていますが、正しい初期値を入れることで、そのことがよりわかりやすくなっています。

    protected $items = [];

次に配列を順に参照する処理をwhile文からforeach文に変更しています。これで添字の抜けなどを考慮せずに、配列の全要素を参照することができます。

        foreach($this->items as $v) {
            $total += $v;
        }

治療ポイント4. PHPセッションの利用

実は、今回の治療で最も大きな変更となったのが、この点です。

ショッピングカートに追加した商品情報(商品コード、個数)は、一時情報としてセッションに保持するのが一般的です。元のコードでは、セッション機能を自作しており[2]⁠、セッションIDの生成から、Cookieへの埋め込み、セッション値のデータベースからの読み取り、書き込みなどが実装されています。セッション機能は機能面だけではなく、セキュリティ面についても配慮して実装する必要があり、これはなかなか骨の折れる作業です。

現在のPHP(PHP 4以降)では、標準でセッション機能があるので、こうした自作をする必要はまったくありません。そこで、PHPのセッション機能を使うように変更しました。

まず、セッションから商品情報を読み込む処理ですが、26行目から始まるコンストラクタでこの処理を行っています。$_SESSION変数を参照して、商品情報が存在すれば、メンバ変数$itemsに代入しています。$_SESSION変数は、セッション情報を操作するスーパーグローバル変数で、セッションの値はすべてこの変数に格納されています。つまり、この変数を参照することでセッション情報を読むことができます。

        if (empty($_SESSION[static::SESSION_KEY])) {
            $this->items = [];
        } else {
            $this->items = $_SESSION[static::SESSION_KEY];
        }

次に、セッションへ商品情報を書き込む処理は、77行目のsave()メソッドで行っています。単に$_SESSION変数にメンバ変数$itemsの値を代入しているだけです。これによりセッションへ商品情報が格納されます。save()メソッドは、addItem()deleteItem()など商品情報を操作する各メソッドから呼び出されるので、商品情報の内容が変化した場合は都度セッションの内容も更新されるようになっています。

        $_SESSION[static::SESSION_KEY] = $this->items;

このようにPHP標準のセッション機能を利用すれば、$_SESSION変数を操作するだけで簡単にセッションを扱うことができます。セッションID生成や、Cookieへの埋め込み、期限切れデータの削除(GC)など自分で実装するにはひと手間がかかる部分もPHPが面倒を見てくれます。PHPでセッションを使う場合は、標準のセッション機構を利用しましょう。

治療を終えて

今回は、PHP 3の時代に実装されたクラスについて診断を行いました。診断したCartクラスは、ショッピングカートというお馴染みの内容で仕様がイメージしやすいものでした。また、コードの書き方に問題点はありましたが、メソッドなどは、ある程度適切に分けられていたので、その点については理解しやすいコードでした。

治療後の修正コードですが、まだ改良の余地があります。より良いコードにすべく考えてみてください。

  • セッション操作を行うクラスを作り、そのインスタンスを使ってセッション操作を行う
  • getTotalCount()メソッドを、PHPの標準関数を使って1行で実装する
  • Acme\NotFoundExceptionクラスは別ファイルにする

今回はここまでです。また次回の診断でお会いしましょう。

おすすめ記事

記事・ニュース一覧