Manual

EC-CUBE 2系

単体テストガイドライン

by TheVOS posted Sep 28, 2019
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
?

Shortcut

PrevPrev Article

NextNext Article

Larger Font Smaller Font Up Down Go comment Print
Extra Form
Original source http://svn.ec-cube.net/open_trac/wiki/EC...9%E3%82%8B

本ガイドラインはEC-CUBEの単体テストをPHPUnitを使って行う上でのガイドラインを
株式会社SHIFT様(http://www.shiftinc.jp/)のご協力によりまとめたものとなります。

各クラス共通のガイドライン

1. テストを含めたフォルダ構成

テストコードを含んだフォルダ構成は以下のようになります。
tests以下には、テストコード本体の他にテスト用のユーティリティや設定ファイル等が含まれます。

build.xmlテストやインスペクションを行うための設定ファイルです。
tests
├phpunit.xmlPHPUnitで使う各種設定を記載したファイルです。
SVN上にはphpunit.xml.baseというファイルがありますが、ローカルではこれをコピーしてphpunit.xmlを作成してください。
├ruleset.xmlPHP_CodeSniffer(インスペクションツール)用の設定ファイルです。
├require.phpテストに必要なファイルをインクルードするためのクラスです。
SVN上にはrequire.php.baseというファイルがありますが、ローカルではこれをコピーしてrequire.phpを作成してください。
└classテスト用のクラスを格納するディレクトリです。
 ├Common_TestCase.php他のテストクラスの基底となるクラスです。
 ├replaceテスト用に実装を入れ替えているクラスを格納するディレクトリです。
 └test/utilテスト用のユーティリティを格納するディレクトリです。

テストコードはそれぞれ対応するソースコードと同じ階層に保存します。

2. テストの実行方法

2.1.  実行の準備(初回のみ)

単体テストを実行するためには、ローカルの環境にPHPUnitをインストールしておく必要があります。
また、インクルードパス等をローカルの環境に合わせて書き換えるため、SVNに含まれているファイルをコピーしてローカル用の設定ファイルを作成する必要があります。
手順は下記の通りです。

  • tests/phpunit.xml.baseをコピーしてtests/phpunit.xmlを作成します。
  • <filter><blacklist>タグ以下の「/usr/local/lib」の部分を、ローカルで使われる各種ライブラリが含まれているパスと置換します。
    • この設定はどのファイルをテストのカバレッジ測定の対象にするかを設定するためのものなので、設定をし直さなくても単体テスト自体は問題なく動作します。
  • tests/require.php.baseをコピーしてtests/require.phpを作成します。
  • PHPUnitのモジュールが使用できるように、インクルードパスを設定します。

tests/phpunit.xml、tests/require.phpはsvn:ignoreに設定されているため、自由に書き換えてもコミットはされません。

2.2.  実行

全体のテストを行う場合には、phingのtestターゲットを実行します。
テストの内容はbuild.xmlの中に定義されているため、実際にはphpunitコマンドが発行されます。

% phing test

テストが完了すると、結果がreportsディレクトリ以下に出力されます。

  • reports/tap.log TAP形式のテスト結果
  • reports/unitreport.xml xUnit形式のテスト結果
  • reports/coverage/coverage.xml カバレッジ測定結果のXML(主にJenkinsで処理するためのものなので気にしなくて良いです)
  • reports/coverage/.html カバレッジ測定結果のHTML

個々のテストを行う場合には、テスト対象のディレクトリを指定してphpunitコマンドを実行します。
標準出力ですぐにテスト結果を確認したい場合にはこちらのやり方のほうが良いでしょう。

% phpunit –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php
% phpunit –c tests/phpunit.xml tests/class/pages

下の例のようにディレクトリを指定した場合には、ディレクトリ以下にあるテストケースが実行されます。

また、--colorsオプションを付けると、結果が色付きで表示され見やすくなります。

% phpunit --colors –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php

カバレッジを測定したい場合には、専用のオプションを指定します。

% phpunit -c tests/phpunit.xml --coverage-html reports/coverage tests/class/pages/LC_Page/LC_Page_InitTest.php

3. テストクラスの構成

テストクラスは、基本的にtests/class/Common_TestCase.phpを継承して作成します。
Common_TestCaseの中には、次節で述べるAssertionを一度に行うverify()関数や
テストの開始時と終了時にDBの準備・後片付けを行うsetUp()/tearDown()関数が含まれています。
個々のテストクラスでは、Common_TestCaseのsetUp()/tearDown()の処理に必要な処理を追加して使います。
また、テストに使用するユーティリティクラスもCommon_TestCaseでまとめてrequireします。

<?php
SampleTest extends Common_TestCase {

  protected function setUp() {
    parent::setUp();
     // 個々のテストケースで必要な処理
  }

  protected function tearDown() {
    // 個々のテストケースで必要な処理
    parent::tearDown();
  }
  public function testFunctionName_❍❍の場合_△△になる() {
    $this->expected = array('hoge', 'fuga');
    $this->actual = array();
    $this->actual[0] = functionName('a');
    $this->actual[1] = functionName('b');

    $this->verify();
  }
}

4. Assertion(期待値の確認)の方法

PHPUnitにはassertEquals()、assertTrue()など様々な期待値の確認用funcitonが存在します。
これらを細かく使用してテストの期待値を確認することもできますが、複数のasseritionを並べると
最初の方で失敗した場合に後のassertionが実行されず、全体の修正までに時間がかかってしまう場合があります。
これを防ぐため、基本的に期待値と実際の結果はarrayに格納して一度でassertできるようにします。
もちろん、それぞれの値が1つずつの場合はarrayに入れなくても構いません。

<?php
protected function verify($msg = null) {
  $this->assertEquals($this->expected, $this->actual, $msg);
}

public function testHoge() {
  $this->expected = array(1, "山田", "太郎");
  // テスト対象を実行して$actualに結果を格納
  $this->verify();
} 

5. テストfunctionの分け方

原則として、1つのテストfunctionで1つの条件をテストします。

  • 良い例
    <?php
    function testAbs_正の値の場合() {
      $expected = 1;
      // テスト対象functionの呼び出し
      $actual = abs(1);
      $this->verify();
    }
    
    function testAbs_負の値の場合() {
      $expected = 2;
      // テスト対象funcitonの呼び出し
      $actual = abs(-2);
      $this->verify();
    }
    
    
  • 悪い例
<?php
function testAbs() {
  $expected[0] = 1;
  $actual[0] = abs(1);

  $expected[1] = 2;
  $actual[1] = -2;

  $this->verify();
}

「悪い例」の書き方の場合、ケースの中身を確認しないと
・何種類のテストを行っているのかが把握できない
・どんな観点でテストを行っているのかが把握できない
といった問題点があります。
1funciton1条件の前提を守った上で、なるべく条件分岐を網羅するようにテストを作成していきます。

6. テストfunctionの命名規則

テストfunctionの名称は、テストの内容を分かりやすくするため

test【function名】_【条件】_【期待する結果】()

という形式で統一します。命名規則を統一することで可読性が上がり、
テストコードを書いた本人でなくてもJenkinsのテストレポートを見るとどのようなテストが行われているかが一目でわかります。
また、条件・期待する結果は日本語で記載することでさらに分かりやすくすることができます。

testAction_必須項目が入力されていない場合_エラー画面に遷移する()

7. テストクラスの分け方

テストクラスはテスト対象のfunction毎に1つずつ分けて作成します。
ただし、後述する「定数による条件分岐」をテストする場合には条件毎にクラスを分ける必要があるためさらに細分化されます。

8.テストクラスの命名規則

上で述べたとおり基本的にテストクラスはテスト対象のfunctionに対応するため、

【対象クラス】_【対象function】Test.php

という名称にします。さらに条件毎にファイルを分ける場合には、

【対象クラス】_【対象function】_【条件】Test.php

とします。

  • LC_Page_Products_Detail_ActionTest.php
    LC_Page_Products_Detail_Action_HasErrorTest.php
    

条件によってファイル名を分ける場合には、ファイル名は日本語を避けて定義するようにしてください。

9. より網羅的にテストを書く方法

1. 定数による条件分岐

defineを使って定義されている定数は、テスト中に自由に上書きすることができません。
そこで、定数の値によって条件が分岐する場合は定数の値ごとにテストコードのファイルを分割します。

  • ソースコード
    <?php
    function sfGetHashString($str, $salt) {
      $res = '';
      if ($salt == '') {
        $salt = AUTH_MAGIC;
      }
      if (AUTH_TYPE == 'PLAIN') {
        $res = $str;
      } else {
        $res = hash_hmac(PASSWORD_HASH_ALGOS, $str . ':' . AUTH_MAGIC, $salt);
      }
      return $res;
    }
    
  • テストコード
    <?php
    $HOME = realpath(dirname(__FILE__)) . "/../../../..";
    // このテスト専用の定数の設定。必ずCommon_TestCase.phpより先に定義する
    define('AUTH_TYPE', 'PLAIN');
    require_once($HOME, "/tests/class/Common_TestCase.php");
    
    // クラス名に条件(authTypePlain)も含める
    class SC_Utils_sfGetHasString_authTypePlainTest extends Common_TestCase {
    
      // AUTH_TYPE=PLAINであることを想定した実際のテストコード
    }
    

このようにCommon_TestCaseより先に指定したい定数を定義することで、このテストクラスに限定した値を定義することができます。

2. exitする箇所のテスト

PHPUnitでテストを行う際には実際にphpのプログラムを走らせることになりますが、
プログラム中にexitする箇所があるとそこでPHPUnit自体も終了してしまうため、有効なテスト結果を得ることができません。
EC-CUBEの場合は、pages以下のクラスでSC_Response_Ex::sendRedirect()やSC_Response_Ex::actionExit()を呼んでいる箇所がそれにあたります。
このような部分をきちんとテストするために、テスト実施時はSC_Response_Exの実装を切り替えてexitしないようにします。
実装を切り替えた後のクラスはtests/class/replace以下に存在します。
このクラスをCommon_TestCaseから呼び出すことにより、テスト時の実装を切り替えます。

<?php
 /**
   * actionExit()呼び出しを書き換えてexit()させない例です。
   */
  public function testExit() {
    $resp = new SC_Response_Ex();
    $resp->actionExit();

    $this->expected = TRUE;
    $this->actual = $resp->isExited();   // exit()したかどうかをチェックします。
    $this->verify('exitしたかどうか');
  }

10. テストを書きやすくする対策

1. functionを「単体」でテストする

functionの中でさらにfunctionが呼ばれている場合、いちばん外側のfunctionをそのまま実行すると中の分岐が多すぎてテストしきれない場合があります。
そのような場合は、内側のfunctionの実装をモックに切り替えて欲しい値を自由に返すようにし、外側のfunctionだけをテストするようにします。

  • ソースコード
    <?php
    class Sample {
    function hoge() {
      if (fuga()) {
        return 1;
      } else {
        return 2;
      }
    }
    
    function fuga() {
       return rand() % 2 == 0;
    }
    }
    
  • テストコード
    <?php
    class SampleTest extends PHPUnit_Framcework_TestCase {
      function testHoge_fugaがtrueの場合() {
        $sample = new Sample_Mock();
        $sample->fuga_val = TRUE;
        $this->assertEquals(1, $sample->hoge());
      }
    }
    
    class Sample_Mock extends Sample {
      $fuga_val;
    
      function fuga() {
        return $fuga_val;
      }
    }
    

この例はそれほど分岐が複雑ではありませんが、内側の関数fuga()の返り値がランダムなのでテストで要求する値を返却させるためにオーバーライドしています。

2. ユーティリティクラスを使ってデータ準備を効率化する

テストで常に同じ結果を得られるようにするには、テスト実行のたびにDBの内容を期待値に合わせてリセットする必要があります。
そのため、EC-CUBEのユーティリティであるSC_Queryクラスを使ってsetUp()の中でデータの準備を行い、tearDown()でロールバックを行います。

<?php
class SampleTest extends PHPUnit_Framework_TestCase {
  // データ準備
  protected function setUp() {
    $this->objQuery = SC_Query_Ex::getSingletonInstance();
    $this->objQuery->begin();
    $this->setUpCustomer();  // 実際にデータを投入する箇所
  }

  // ロールバック
  protected function tearDown() {
    $this->objQuery->rollback();
  }

  // データの定義
  protected function setUpCustomer() {
        $arrValue['customer_id'] = $this->customer_id;
        $arrValue['name01'] = $this->name01;
        $arrValue['name02'] = $this->name02;
        $arrValue['kana01'] = $this->name01;
        $arrValue['email'] = 'test@example.com';
        $arrValue['secret_key'] = 'aaaaaa';
        $arrValue['status'] = 2; // 会員
        $arrValue['create_date'] = 'CURRENT_TIMESTAMP';
        $arrValue['update_date'] = 'CURRENT_TIMESTAMP';
        $this->objQuery->insert('dtb_customer', $arrValue);
  }
}

よく使うデータ定義はtests/class/util以下のユーティリティクラスから取得できるようにしておき、データの再利用性を高めます。

3. ユーティリティクラスを使って端末の種類を設定する

端末の種類がPC・モバイル・スマートフォンのいずれになっているかによって条件が分岐する場合は、
テスト専用のユーティリティを使用して擬似的に端末の種別を設定します。ユーティリティはtests/class/test/util/User_Utils.phpに定義されています。

<?php
/**
   * 端末種別をテストケースから自由に設定する例です。
   */
  public function testDeviceType() {
    $this->expected = array(DEVICE_TYPE_MOBILE, DEVICE_TYPE_SMARTPHONE);
    $this->actual = array();

    // 端末種別を設定
    User_Utils::setDeviceType(DEVICE_TYPE_MOBILE);
    $this->actual[0] = SC_Display_Ex::detectDevice();
    User_Utils::setDeviceType(DEVICE_TYPE_SMARTPHONE);
    $this->actual[1] = SC_Display_Ex::detectDevice();

    $this->verify('端末種別');
  }

4. ユーティリティクラスを使ってログイン状態を設定する

ユーザがログインしているかどうかによって処理が分岐する場合は、セッションの情報とDBの値を書き換えることによりテストで要求する分岐を実現します。
情報を書き換えるfunctionは、端末種別設定と同じくtests/class/test/util/User_Utils.php内で定義されています。

<?php
  /**
   * ログイン状態をテストケースから自由に切り替える例です。
   */
  public function testLoginState() {
    $this->expected = array(FALSE, TRUE);
    $this->actual = array();

    $objCustomer = new SC_Customer_Ex();
    // ログインしていない状態に設定
    User_Utils::setLoginState(FALSE);
    $this->actual[0] = $objCustomer->isLoginSuccess();
    // ログインしている状態に設定
    User_Utils::setLoginState(TRUE, null, $this->objQuery);
    $this->actual[1] = $objCustomer->isLoginSuccess();

    $this->verify('ログイン状態');
  }


Facebook [en]Comment 


  • Q: [Class] 클래스와 객체의 기초 TheVOS 2019.10.03
    A:

    クラス(class)とオブジェクト(object)

    オブジェクト(object)とは、実生活では、我々が認識することができるもので理解することができます。

    これらのオブジェクトの状態(state)と行動(behavior)は、それぞれのプロパティ(property)とメソッド(method)に実装されます。


    また、オブジェクト(object)を作り出すための枠組みや設計図のような概念がまさにクラス(class)です。

    つまり、PHPでは、クラスを持っているオブジェクトを作成して使用します。


    次の例では、Carクラスのオブジェクト(object)を示す例です。


    クラス(class)

    - 車(Car)

    スポーツカー


    プロパティ(property)

    - $car->modelName = "フェラーリNYIAS"

    - $car->modelYear = 2012

    - $car->color = "黄色"

    - $car->maxSpeed = 206 mph


    メソッド(method)

    - $car->accelerate()

    - $car->brake()


    インスタンス(instance)

    - 私の車(myCar)


    自動車インスタンスは、すべて上記のようなプロパティとメソッドを持つでしょう。

    しかし、各プロパティの値はインスタンスにすべて異なります。


    プログラミングでインスタンス(instance)とは、メモリ上に作成されたオブジェクトを意味します。

    オブジェクト指向プログラミング(OOP、Object-Oriented Programming)

    オブジェクト指向プログラミングでは、すべてのデータをオブジェクト(object)として扱われ、オブジェクトがすぐにプログラミングの中心となります。

    これにより、コードの管理が容易になり、より少ない労力でも簡単にコードを変更すると、維持管理することができます。


    オブジェクト指向プログラミングが持つ特徴は次のとおりです。


    1. 抽象化(abstraction)
    2. カプセル化(encapsulation)
    3. 情報秘匿(data hiding)
    4. 継承性(inheritance)
    5. ポリモーフィズム(polymorphism)

    カプセル化は、すでに作成されたコードを変更せずに再使用することを目的とします。

    また、情報秘匿を介してオブジェクトの実際の実装内容を外部で知られないよう隠し、オブジェクトのインタフェースを介してのみデータにアクセスできるようにして、セキュリティを強化します。

    継承は、クラス間の階層関係を作って、論理的かつ体系的に他のクラスの機能とデータを使用できるようになります。

    また、ポリモーフィズムを介して一つの変数や関数の名前が、状況に応じて別の意味で解釈されることができるようになります。

    Facebook [en]Comment 

  • Q: [EC-CUBE 2.x] EC-CUBE:SC_FormParamクラスによるパラメーターチェック方法 TheVOS 2019.10.03
    A:

    今お仕事で関わっているEC-CUBEですが、ネット上にはまだまだ情報が少ないです。

    例えばプラグイン作成でオリジナルフォームを作っても、SC_FormParamクラスを使って、どうやってチェックしたらいいのか調べてもなかなか出て来ません。
    そこで私が独自で調べた中から、少しづつ情報提供していきたいと思います。

    ※:EC-CUBEのバージョンは2.12.2のものをベースにしています。

    SC_FormParamの基本的な使い方

    SC_FormParamクラスを使うには、拡張先であるSC_FormParam_Exクラスをインスタンス化して利用します。 ここではPOSTされたデータを設定してみましょう。

    $objFormParam = new SC_FormParam_Ex();
    $objFormParam->setParam($_POST);
    

    次に、内容の検証ルールを追加します。
    ここでは例として名前(性、名)とメールアドレスを必須入力にし、メールアドレスはちゃんとした形式になっているもののみ受け入れるようにしてみます。

    $objFormParam->addParam('姓', 'sei', '', '', array('EXIST_CHECK','NO_SPTAB'));
    $objFormParam->addParam('名', 'mei', '', '', array('EXIST_CHECK','NO_SPTAB'));
    $objFormParam->addParam('メールアドレス', 'email', '', '', array('EMAIL_CHECK'));
    

    後半に挙げる「オプション」が必要な検証ルールについては、次のようにaddParam()の3番目の引数に設定します。

    //姓を3文字以内にしたい場合
    $objFormParam->addParam('姓, 'sei', 3, '', array('MAX_LENGTH_CHECK'));
    

    そして最後に検証作業。
    エラーがあると$arrErr変数にエラーが配列で格納されます。

    $arrErr = $objFormParam->checkError();
    

    検証ルールの最後のパラメーターにある配列型の文字のところがポイントなんですけど、ここに様々なルールを追加していくことでいろんな検証ルールを作ることができます。 以下に検証ルール一覧を載せてみたので実際に組み合わせて試してみてください。

    $objFormParam->addParam()の4番目の引数は入力値をmb_convert_kana()で変換するための変換オプション文字列を指定します。 変換オプションを指定した場合は、checkError()を実行する前に「$objFormParam->convParam()」を実行する必要があるので注意してください。

    SC_FormParam検証ルール一覧

    チェック項目 定数名 オプション エラー条件
    必須入力の判定 EXIST_CHECK   値の受け取りがない場合エラーを返す
    数字の判定 NUM_CHECK   入力文字が数字以外ならエラーを返す
    メールアドレス形式の判定 EMAIL_CHECK   メールアドレス形式でないならエラーを返す
    メールアドレスに使用できる文字の判定 EMAIL_CHAR_CHECK   メールアドレスに使用できない文字が含まれていたらエラーを返す
    携帯メールアドレスの判定 MOBILE_EMAIL_CHECK   SC_Helper_Mobile_Ex::gfIsMobileMailAddress()による判定結果でエラーを返す
    英字の判定 ALPHA_CHECK   入力文字が半角英字以外ならエラーを返す
    英数字の判定 ALNUM_CHECK   入力文字が英数字以外ならエラーを返す
    英数記号の判定 GRAPH_CHECK   入力文字が英数記号以外ならエラーを返す
    カタカナの判定 KANA_CHECK   入力文字がカナ以外ならエラーを返す
    カタカナの判定2 KANABLANK_CHECK   入力文字がカナ以外ならエラーを返す (タブ、スペースは許可する)
    URL形式の判定 URL_CHECK   URLを正規表現で判定する。デフォルトでhttp://があってもOK
    IPアドレスの判定 IP_CHECK   IPアドレスでない場合エラーを返す。改行コードが含まれている場合には配列に変換
    ドメインチェック DOMAIN_CHECK   ドメインの形式が正しくなければエラーを返す
    スペース、タブのみの判定 SPTAB_CHECK   スペース、タブ、改行のみの入力の場合エラーを返す
    スペース、タブの判定 NO_SPTAB   入力文字がスペース、タブ、改行を含んでいたらエラーを返す
    数字(非ゼロ)の判定 ZERO_CHECK   数値入力値で0が入力された場合エラーを返す
    ゼロで開始されている数値の判定 ZERO_START   0で始まる数値が入力されていたらエラーを返す
    ファイルの存在チェック FILE_EXISTS   入力パスのファイルが見つからなければエラーを返す
    ディレクトリ内のファイル存在チェック FIND_FILE ディレクトリ 指定ディレクトリ内に入力パスのファイルが見つからなければエラーを返す(デフォルトでIMAGE_SAVE_REALDIR)
    ディレクトリ存在チェック DIR_CHECK   入力されたパス文字のディレクトリが見つからなければエラーを返す
    ファイル名の判定 FILE_NAME_CHECK   入力文字が英数字,’_’,’-‘以外ならエラーを返す
    ファイル名の判定(アップロード以外の時) FILE_NAME_CHECK_BY_NOUPLOAD   入力文字が英数字,’_’,’-‘以外ならエラーを返す
    最大文字数制限の判定 MAX_LENGTH_CHECK 最大文字数 入力が指定文字数より大きいならエラーを返す
    最小文字数制限の判定 MIN_LENGTH_CHECK 最小文字数 入力が指定文字数未満ならエラーを返す
    桁数の判定 NUM_COUNT_CHECK 桁数 入力文字の桁数が指定の桁数でなければエラーを返す
    必須選択の判定 SELECT_CHECK   プルダウンなどで選択されていない場合エラーを返す
    小文字に変換 CHANGE_LOWER   入力文字を小文字に変換する
    ダウンロード用ファイルの存在チェック DOWN_FILE_EXISTS   DOWN_SAVE_REALDIR内に入力パスのファイルが見つからなければエラーを返す

    お役に立てば幸いです。

    Facebook [en]Comment 

  • Q: [EC-CUBE Operation Manual] EC-CUBE2.12 運用マニュアル TheVOS 2019.09.29
    A:

    EC-CUBE 

    Ver2120 

    Ver2121 

    Ver2122 

    運用マニュアル

    2012年11月28日




    目次

    開店準備4
    基本情報設定4
    SHOPマスターの設定4
    特定商取引に基づく表記の登録5
    配送方法の設定6
    支払い方法の設定8
    ポイントの設定9
    受注受付メールの設定9
    会員規約の設定11
    郵便番号DB登録11
    SEO管理13
    定休日管理13
    商品登録14
    カテゴリーの登録14
    メーカーの登録15
    商品の登録15
    規格の登録19
    商品並び替え21
    運用22
    受注管理22
    受注管理22
    対応状況管理25
    会員管理26
    会員マスター26
    売上集計28
    売上集計の参照28
    メルマガ管理30
    テンプレート設定30
    配信内容設定32
    コンテンツ管理34
    新着情報管理34
    おすすめ商品管理36
    ファイル管理38



    目次

    ファイル管理38
    CSV出力設定39
    レビュー管理41
    レビュー管理41
    デザイン管理42
    レイアウト管理42
    ページ詳細設定45
    ブロック設定46
    ヘッダー / フッダーの編集47
    CSS設定48
    テンプレート追加49
    テンプレート設定50
    システム設定51
    ユーザ管理51
    バックアップ管理52
    プラグイン管理53




    Facebook [en]Comment 

  • Q: [EC-CUBE 2系] 単体テストガイドライン TheVOS 2019.09.28
    A:

    本ガイドラインはEC-CUBEの単体テストをPHPUnitを使って行う上でのガイドラインを
    株式会社SHIFT様(http://www.shiftinc.jp/)のご協力によりまとめたものとなります。

    各クラス共通のガイドライン

    1. テストを含めたフォルダ構成

    テストコードを含んだフォルダ構成は以下のようになります。
    tests以下には、テストコード本体の他にテスト用のユーティリティや設定ファイル等が含まれます。

    build.xmlテストやインスペクションを行うための設定ファイルです。
    tests
    ├phpunit.xmlPHPUnitで使う各種設定を記載したファイルです。
    SVN上にはphpunit.xml.baseというファイルがありますが、ローカルではこれをコピーしてphpunit.xmlを作成してください。
    ├ruleset.xmlPHP_CodeSniffer(インスペクションツール)用の設定ファイルです。
    ├require.phpテストに必要なファイルをインクルードするためのクラスです。
    SVN上にはrequire.php.baseというファイルがありますが、ローカルではこれをコピーしてrequire.phpを作成してください。
    └classテスト用のクラスを格納するディレクトリです。
     ├Common_TestCase.php他のテストクラスの基底となるクラスです。
     ├replaceテスト用に実装を入れ替えているクラスを格納するディレクトリです。
     └test/utilテスト用のユーティリティを格納するディレクトリです。

    テストコードはそれぞれ対応するソースコードと同じ階層に保存します。

    2. テストの実行方法

    2.1.  実行の準備(初回のみ)

    単体テストを実行するためには、ローカルの環境にPHPUnitをインストールしておく必要があります。
    また、インクルードパス等をローカルの環境に合わせて書き換えるため、SVNに含まれているファイルをコピーしてローカル用の設定ファイルを作成する必要があります。
    手順は下記の通りです。

    • tests/phpunit.xml.baseをコピーしてtests/phpunit.xmlを作成します。
    • <filter><blacklist>タグ以下の「/usr/local/lib」の部分を、ローカルで使われる各種ライブラリが含まれているパスと置換します。
      • この設定はどのファイルをテストのカバレッジ測定の対象にするかを設定するためのものなので、設定をし直さなくても単体テスト自体は問題なく動作します。
    • tests/require.php.baseをコピーしてtests/require.phpを作成します。
    • PHPUnitのモジュールが使用できるように、インクルードパスを設定します。

    tests/phpunit.xml、tests/require.phpはsvn:ignoreに設定されているため、自由に書き換えてもコミットはされません。

    2.2.  実行

    全体のテストを行う場合には、phingのtestターゲットを実行します。
    テストの内容はbuild.xmlの中に定義されているため、実際にはphpunitコマンドが発行されます。

    % phing test
    

    テストが完了すると、結果がreportsディレクトリ以下に出力されます。

    • reports/tap.log TAP形式のテスト結果
    • reports/unitreport.xml xUnit形式のテスト結果
    • reports/coverage/coverage.xml カバレッジ測定結果のXML(主にJenkinsで処理するためのものなので気にしなくて良いです)
    • reports/coverage/.html カバレッジ測定結果のHTML

    個々のテストを行う場合には、テスト対象のディレクトリを指定してphpunitコマンドを実行します。
    標準出力ですぐにテスト結果を確認したい場合にはこちらのやり方のほうが良いでしょう。

    % phpunit –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php
    % phpunit –c tests/phpunit.xml tests/class/pages
    

    下の例のようにディレクトリを指定した場合には、ディレクトリ以下にあるテストケースが実行されます。

    また、--colorsオプションを付けると、結果が色付きで表示され見やすくなります。

    % phpunit --colors –c tests/phpunit.xml tests/class/pages/LC_Page/LC_Page_InitTest.php
    

    カバレッジを測定したい場合には、専用のオプションを指定します。

    % phpunit -c tests/phpunit.xml --coverage-html reports/coverage tests/class/pages/LC_Page/LC_Page_InitTest.php
    

    3. テストクラスの構成

    テストクラスは、基本的にtests/class/Common_TestCase.phpを継承して作成します。
    Common_TestCaseの中には、次節で述べるAssertionを一度に行うverify()関数や
    テストの開始時と終了時にDBの準備・後片付けを行うsetUp()/tearDown()関数が含まれています。
    個々のテストクラスでは、Common_TestCaseのsetUp()/tearDown()の処理に必要な処理を追加して使います。
    また、テストに使用するユーティリティクラスもCommon_TestCaseでまとめてrequireします。

    <?php
    SampleTest extends Common_TestCase {
    
      protected function setUp() {
        parent::setUp();
         // 個々のテストケースで必要な処理
      }
    
      protected function tearDown() {
        // 個々のテストケースで必要な処理
        parent::tearDown();
      }
      public function testFunctionName_❍❍の場合_△△になる() {
        $this->expected = array('hoge', 'fuga');
        $this->actual = array();
        $this->actual[0] = functionName('a');
        $this->actual[1] = functionName('b');
    
        $this->verify();
      }
    }
    

    4. Assertion(期待値の確認)の方法

    PHPUnitにはassertEquals()、assertTrue()など様々な期待値の確認用funcitonが存在します。
    これらを細かく使用してテストの期待値を確認することもできますが、複数のasseritionを並べると
    最初の方で失敗した場合に後のassertionが実行されず、全体の修正までに時間がかかってしまう場合があります。
    これを防ぐため、基本的に期待値と実際の結果はarrayに格納して一度でassertできるようにします。
    もちろん、それぞれの値が1つずつの場合はarrayに入れなくても構いません。

    <?php
    protected function verify($msg = null) {
      $this->assertEquals($this->expected, $this->actual, $msg);
    }
    
    public function testHoge() {
      $this->expected = array(1, "山田", "太郎");
      // テスト対象を実行して$actualに結果を格納
      $this->verify();
    } 
    

    5. テストfunctionの分け方

    原則として、1つのテストfunctionで1つの条件をテストします。

    • 良い例
      <?php
      function testAbs_正の値の場合() {
        $expected = 1;
        // テスト対象functionの呼び出し
        $actual = abs(1);
        $this->verify();
      }
      
      function testAbs_負の値の場合() {
        $expected = 2;
        // テスト対象funcitonの呼び出し
        $actual = abs(-2);
        $this->verify();
      }
      
      
    • 悪い例
    <?php
    function testAbs() {
      $expected[0] = 1;
      $actual[0] = abs(1);
    
      $expected[1] = 2;
      $actual[1] = -2;
    
      $this->verify();
    }
    

    「悪い例」の書き方の場合、ケースの中身を確認しないと
    ・何種類のテストを行っているのかが把握できない
    ・どんな観点でテストを行っているのかが把握できない
    といった問題点があります。
    1funciton1条件の前提を守った上で、なるべく条件分岐を網羅するようにテストを作成していきます。

    6. テストfunctionの命名規則

    テストfunctionの名称は、テストの内容を分かりやすくするため

    test【function名】_【条件】_【期待する結果】()
    

    という形式で統一します。命名規則を統一することで可読性が上がり、
    テストコードを書いた本人でなくてもJenkinsのテストレポートを見るとどのようなテストが行われているかが一目でわかります。
    また、条件・期待する結果は日本語で記載することでさらに分かりやすくすることができます。

    testAction_必須項目が入力されていない場合_エラー画面に遷移する()
    

    7. テストクラスの分け方

    テストクラスはテスト対象のfunction毎に1つずつ分けて作成します。
    ただし、後述する「定数による条件分岐」をテストする場合には条件毎にクラスを分ける必要があるためさらに細分化されます。

    8.テストクラスの命名規則

    上で述べたとおり基本的にテストクラスはテスト対象のfunctionに対応するため、

    【対象クラス】_【対象function】Test.php
    

    という名称にします。さらに条件毎にファイルを分ける場合には、

    【対象クラス】_【対象function】_【条件】Test.php
    

    とします。

    • LC_Page_Products_Detail_ActionTest.php
      LC_Page_Products_Detail_Action_HasErrorTest.php
      

    条件によってファイル名を分ける場合には、ファイル名は日本語を避けて定義するようにしてください。

    9. より網羅的にテストを書く方法

    1. 定数による条件分岐

    defineを使って定義されている定数は、テスト中に自由に上書きすることができません。
    そこで、定数の値によって条件が分岐する場合は定数の値ごとにテストコードのファイルを分割します。

    • ソースコード
      <?php
      function sfGetHashString($str, $salt) {
        $res = '';
        if ($salt == '') {
          $salt = AUTH_MAGIC;
        }
        if (AUTH_TYPE == 'PLAIN') {
          $res = $str;
        } else {
          $res = hash_hmac(PASSWORD_HASH_ALGOS, $str . ':' . AUTH_MAGIC, $salt);
        }
        return $res;
      }
      
    • テストコード
      <?php
      $HOME = realpath(dirname(__FILE__)) . "/../../../..";
      // このテスト専用の定数の設定。必ずCommon_TestCase.phpより先に定義する
      define('AUTH_TYPE', 'PLAIN');
      require_once($HOME, "/tests/class/Common_TestCase.php");
      
      // クラス名に条件(authTypePlain)も含める
      class SC_Utils_sfGetHasString_authTypePlainTest extends Common_TestCase {
      
        // AUTH_TYPE=PLAINであることを想定した実際のテストコード
      }
      

    このようにCommon_TestCaseより先に指定したい定数を定義することで、このテストクラスに限定した値を定義することができます。

    2. exitする箇所のテスト

    PHPUnitでテストを行う際には実際にphpのプログラムを走らせることになりますが、
    プログラム中にexitする箇所があるとそこでPHPUnit自体も終了してしまうため、有効なテスト結果を得ることができません。
    EC-CUBEの場合は、pages以下のクラスでSC_Response_Ex::sendRedirect()やSC_Response_Ex::actionExit()を呼んでいる箇所がそれにあたります。
    このような部分をきちんとテストするために、テスト実施時はSC_Response_Exの実装を切り替えてexitしないようにします。
    実装を切り替えた後のクラスはtests/class/replace以下に存在します。
    このクラスをCommon_TestCaseから呼び出すことにより、テスト時の実装を切り替えます。

    <?php
     /**
       * actionExit()呼び出しを書き換えてexit()させない例です。
       */
      public function testExit() {
        $resp = new SC_Response_Ex();
        $resp->actionExit();
    
        $this->expected = TRUE;
        $this->actual = $resp->isExited();   // exit()したかどうかをチェックします。
        $this->verify('exitしたかどうか');
      }
    

    10. テストを書きやすくする対策

    1. functionを「単体」でテストする

    functionの中でさらにfunctionが呼ばれている場合、いちばん外側のfunctionをそのまま実行すると中の分岐が多すぎてテストしきれない場合があります。
    そのような場合は、内側のfunctionの実装をモックに切り替えて欲しい値を自由に返すようにし、外側のfunctionだけをテストするようにします。

    • ソースコード
      <?php
      class Sample {
      function hoge() {
        if (fuga()) {
          return 1;
        } else {
          return 2;
        }
      }
      
      function fuga() {
         return rand() % 2 == 0;
      }
      }
      
    • テストコード
      <?php
      class SampleTest extends PHPUnit_Framcework_TestCase {
        function testHoge_fugaがtrueの場合() {
          $sample = new Sample_Mock();
          $sample->fuga_val = TRUE;
          $this->assertEquals(1, $sample->hoge());
        }
      }
      
      class Sample_Mock extends Sample {
        $fuga_val;
      
        function fuga() {
          return $fuga_val;
        }
      }
      

    この例はそれほど分岐が複雑ではありませんが、内側の関数fuga()の返り値がランダムなのでテストで要求する値を返却させるためにオーバーライドしています。

    2. ユーティリティクラスを使ってデータ準備を効率化する

    テストで常に同じ結果を得られるようにするには、テスト実行のたびにDBの内容を期待値に合わせてリセットする必要があります。
    そのため、EC-CUBEのユーティリティであるSC_Queryクラスを使ってsetUp()の中でデータの準備を行い、tearDown()でロールバックを行います。

    <?php
    class SampleTest extends PHPUnit_Framework_TestCase {
      // データ準備
      protected function setUp() {
        $this->objQuery = SC_Query_Ex::getSingletonInstance();
        $this->objQuery->begin();
        $this->setUpCustomer();  // 実際にデータを投入する箇所
      }
    
      // ロールバック
      protected function tearDown() {
        $this->objQuery->rollback();
      }
    
      // データの定義
      protected function setUpCustomer() {
            $arrValue['customer_id'] = $this->customer_id;
            $arrValue['name01'] = $this->name01;
            $arrValue['name02'] = $this->name02;
            $arrValue['kana01'] = $this->name01;
            $arrValue['email'] = 'test@example.com';
            $arrValue['secret_key'] = 'aaaaaa';
            $arrValue['status'] = 2; // 会員
            $arrValue['create_date'] = 'CURRENT_TIMESTAMP';
            $arrValue['update_date'] = 'CURRENT_TIMESTAMP';
            $this->objQuery->insert('dtb_customer', $arrValue);
      }
    }
    

    よく使うデータ定義はtests/class/util以下のユーティリティクラスから取得できるようにしておき、データの再利用性を高めます。

    3. ユーティリティクラスを使って端末の種類を設定する

    端末の種類がPC・モバイル・スマートフォンのいずれになっているかによって条件が分岐する場合は、
    テスト専用のユーティリティを使用して擬似的に端末の種別を設定します。ユーティリティはtests/class/test/util/User_Utils.phpに定義されています。

    <?php
    /**
       * 端末種別をテストケースから自由に設定する例です。
       */
      public function testDeviceType() {
        $this->expected = array(DEVICE_TYPE_MOBILE, DEVICE_TYPE_SMARTPHONE);
        $this->actual = array();
    
        // 端末種別を設定
        User_Utils::setDeviceType(DEVICE_TYPE_MOBILE);
        $this->actual[0] = SC_Display_Ex::detectDevice();
        User_Utils::setDeviceType(DEVICE_TYPE_SMARTPHONE);
        $this->actual[1] = SC_Display_Ex::detectDevice();
    
        $this->verify('端末種別');
      }
    

    4. ユーティリティクラスを使ってログイン状態を設定する

    ユーザがログインしているかどうかによって処理が分岐する場合は、セッションの情報とDBの値を書き換えることによりテストで要求する分岐を実現します。
    情報を書き換えるfunctionは、端末種別設定と同じくtests/class/test/util/User_Utils.php内で定義されています。

    <?php
      /**
       * ログイン状態をテストケースから自由に切り替える例です。
       */
      public function testLoginState() {
        $this->expected = array(FALSE, TRUE);
        $this->actual = array();
    
        $objCustomer = new SC_Customer_Ex();
        // ログインしていない状態に設定
        User_Utils::setLoginState(FALSE);
        $this->actual[0] = $objCustomer->isLoginSuccess();
        // ログインしている状態に設定
        User_Utils::setLoginState(TRUE, null, $this->objQuery);
        $this->actual[1] = $objCustomer->isLoginSuccess();
    
        $this->verify('ログイン状態');
      }


    Facebook [en]Comment 

  • Q: [EC-CUBE 2系] リファクタリングガイドライン TheVOS 2019.09.28
    A:

    init 関数

    init 関数は, クラスの初期化を目的する. ビジネスロジックの記述はしないこと

    <?php
    function init() {
    
        /**
         * NG ビジネスロジックの記述はしない
         */
        if (count($this->arrPayment) > 0) {
            $i = 0;
            foreach ($this->arrPayment as $val) {
                $this->payment[$i] = $val;
                $i++;
            }
        }
    
        /**
         * OK クラスの初期化のみ行う
         */
        $this->tpl_mainpage = 'index.tpl';
        $this->arrDISP = $masterData->getMasterData('mtb_disp');
    }
    

    action 関数

    action 関数は MVC のコントローラにあたり以下の処理を記述する.

    • 条件分岐
    • VIEW からの入力
    • VIEW に渡すメンバ変数($this->variable_name)への代入
    • 宣言
    • ビジネスロジックの呼び出し

    action 関数に, ビジネスロジックを直接記述したり, SQL を実行する処理を記述しないこと.

    <?php
    /**
     * action 関数のサンプル
     */
    function action() {
    
        /** OK VIEW からの入力を処理する */
        $this->arrForm = $this->lfConvertParam($_POST);
    
        /** OK 入力に応じて条件分岐する */
        switch ($this->getMode()) {
        case 'edit':
    
            /** OK エラーの内容に応じて条件分岐する */
            $this->arrErr = $this->lfProductClassError($this->arrForm);
            if (empty($this->arrErr)){
                $this->tpl_mainpage = 'products/product_class_confirm.tpl';
                $this->lfProductConfirmPage($this->arrForm);
            } else {
                $this->doPreEdit($this->arrForm, false ,true);
            }
            break;
    
        case 'delete':
            $this->doDelete($this->arrForm);
            break;
    
        case 'disp':
            /** NG ビジネスロジックの記述をしてはならない */
            foreach ($this->arrForm as $key => $val) {
                $foo[$key] = $val;
            }
    
            /** NG SQLを実行する処理を記述してはならない */
            $objQuery = SC_Query::getSingletonInstance();
            $arrResults = $objQuery->select('*', 'table_name');
    
            $this->doDisp($this->arrForm);
            break;
    
        case 'complete':
            $this->registerProductClass($this->arrForm, $this->arrForm['product_id']);
            SC_Response_Ex::sendRedirect('complete.php');
            break;
    
        default:
        }
    }
    

    MODE パラメータ

    $_POST['mode'] や $_GET['mode'] に対する条件分岐は, LC_Page::getMode() を使用し, switch 文で記述する.

    <?php
    /** OK switch 文で mode の分岐を行う */
    switch ($this->getMode()) {
    case 'disp':
        $this->doDisp($this->arrForm);
        break;
    
    case 'complete':
        SC_Response_Ex::sendRedirect('complete.php');
        break;
    default:
    }
    
    /** NG mode の条件分岐に if 文を使用してはならない */
    if ($_POST['mode'] == 'disp') {
        $this->doDisp($this->arrForm);
    } elseif ($_POST['mode'] == 'complete') {
        SC_Response_Ex::sendRedirect('complete.php');
    }
    

    ビジネスロジック

    • ページ間で重複する処理が存在する場合は, Helper などの共通クラスへ記述する
    • ページ固有の処理は, ローカル関数で記述する
    • 可能であれば, PHPUnit を使用してテストケースを残すこと
    • 宣言を除き, 引数や返り値が無く, すべて内部のメンバ変数で処理するような関数は極力作成しない
      • ステートレスな処理を心掛けること
    <?php
    
    /**
     * NG 引数ではなく, クラスのメンバ変数を使用して振舞いを変更する
     */
    function lfSendMail() {
         $objPurchase = new SC_Purchase_Ex();
         $objPurchase->sendOrderMail($this->order_id)
    }
    
    /**
     * OK 引数で振舞いを変更できる
     */
    function lfSendMail($order_id) {
         $objPurchase = new SC_Purchase_Ex();
         $objPurchase->sendOrderMail($order_id)
    }
    
    /**
     * NG 引数や返り値が無く, メンバ変数の値を直接変更している
     */
    function lfConvertLoginPass(){
        if(strlen($this->arrForm['login_pass']) < 1 ) {
            return;
        }
        $this->arrForm['login_pass'] = trim($this->arrForm['login_pass']);
        $this->arrForm['login_pass1'] = $this->arrForm['login_pass'];
        $this->arrForm['login_pass2'] = $this->arrForm['login_pass'];
    }
    
    /**
     * OK ローカル関数内では, ローカル変数を使用することで, 拡張性, 保守性が向上し, ユニットテストも書きやすい
     */
    
    $this->arrForm = $this->lfConvertLoginPass($this->arrForm);
    
    function lfConvertLoginPass(&$arrForm){
        if(strlen($arrForm['login_pass']) < 1 ) {
            return;
        }
        $arrForm['login_pass'] = trim($arrForm['login_pass']);
        $arrForm['login_pass1'] = $arrForm['login_pass'];
        $arrForm['login_pass2'] = $arrForm['login_pass'];
        return $arrForm;
    }
    
    /**
     * ユニットテストの例.
     */
    function testLfConvertLoginPass() {
        $login_pass = 'login_pass_value';
        $arrForm = array('login_pass' => $login_pass);
    
        $expected = array('login_pass' => $login_pass,
                          'login_pass1' => $login_pass,
                          'login_pass2' => $login_pass);
    
        $actual = lfConvertLoginPass($arrForm);
    
        $this->assertEquals($expected, $actual);
    }
    
    • ループ処理などで, メンバ変数や, スーパーグローバル変数は直接使わない
    <?php
    
    /**
     * NG ループ処理で, メンバ変数やスーパーグローバル変数を直接扱っている
     */
    foreach ($_POST['params'] as $key => $val) {
         $this->arrForm[$key] = $val;
    }
    
    /**
     * OK メンバ変数や, スーパーグローバル変数に作用するループ処理は, 別途関数を作成する
     */
    $this->arrParams = $this->getParams($_POST['params']);
    
    function getParams($arrParams) {
         $arrResults = array();
         foreach ($arrParams as $key => $val) {
             $arrResults[$key] = $val;
         }
         return $arrResults;
    }
    

    データベースアクセス

    特に理由の無い場合は SC_Query::getSingletonInstance() を使用してインスタンスを取得すること

    <?php
    
    /**
     * NG PHP4 環境では, 新たなインスタンスが生成されてしまう
     */
    $objQuery = new SC_Query();
    
    /**
     * OK シングルトンが保証され, トランザクションの扱いが容易になる
     */
    $objQuery = SC_Query::getSingletonInstance();
    

    SQL 文を散乱させない. SELECT id, name, foo, bar FROM table_name と SELECT id, name, foo, bar, too FROM table_name が存在する場合は, 後者を共通関数にリファクタリングすること

    <?php
    
    /**
     * NG 類似した SQL を散乱させてはいけない
     */
    $arrResults = $objQuery->select('id, name, foo, bar', 'table_name');
    $arrResult2 = $objQuery->select('id, name, foo, bar, too', 'table_name', 'too = ?', $arrParams['too']);
    
    /**
     * OK カラム名や, WHERE の違いなどは共通関数で吸収する
     */
    function getResults($arrParams = array()) {
        $where = '';
        $arrValues = array();
        if (isset($arrParams['too'])) {
            $where .= 'too = ?';
            $arrValues[] = $arrParams['too'];
    
        }
        $objQuery = SC_Query::getSingletonInstance();
        $arrResults = $objQuery->select('*', 'table_name', $where, $arrValues);
        return $arrResults;
    }
    $arrResults = getResults();
    $arrResults2 = getResults($arrParams);
    

    INSERT/UPDATE/DELETE は, SC_Query::insert(), SC_Query::update(), SC_Query::delete() を使用し, SC_Query::query() は使用しないこと

    <?php
    
    /** NG SC_Query::query() は使用しない */
    $objQuery = SC_Query::getSingletonInstance();
    $objQuery->query('INSERT INTO table_name (col1, col2) VALUES (?, ?)', arrary($col1, $col2));
    
    /** OK SC_Query::insert(), SC_Query::update(),  SC_Query::delete() を使用する */
    $objQuery = SC_Query::getSingletonInstance();
    $objQuery->insert('table_name', array('col1' => $col1,
                                          'col2' => $col2));
    

    LIMIT, OFFSET は SC_Query::setLimit(), SC_Query::setLimitOffset() を使用すること.

    <?php
    
    /** NG LIMIT, OFFSET を直接使用しない */
    $objQuery = SC_Query::getSingletonInstance();
    $arrResults = $objQuery->getAll('SELECT * FROM table_name WHERE del_flg = 0 LIMIT 10 OFFSET 5');
    
    /** OK SC_Query::setLimit(), SC_Query::setLimitOffset() を使用する */
    $objQuery = SC_Query::getSingletonInstance();
    $objQuery->setLimitOffset(10, 5);
    $arrResults = $objQuery->select('*', 'table_name', 'del_flg = ?', array('0'));
    

    RDBMS の可搬性向上のため USING 句は使用しないこと

    <?php
    
    /** NG USING 句は使用しない */
    $objQuery = SC_Query::getSingletonInstance();
    $arrResults = $objQuery->select('T1.*', 'table_name T1 JOIN table2_name T2 USING(col)');
    
    /** OK ON 句 を使用する */
    $objQuery = SC_Query::getSingletonInstance();
    $arrResults = $objQuery->select('T1.*', 'table_name T1 JOIN table2_name T2 ON T1.col = T2.col');
    

    入力チェック

    入力チェック用のクラスには, SC_FromParam と SC_CheckError が存在するが, 極力 SC_FormParam を使用すること

    端末種別の判別

    Net_UserAgent_Mobile::isMobile() や MOBILE_SITE 定数など存在するが, SC_Display_Ex::detectDevice() に統一する

    スーパーグローバル変数

    $_POST や $_GET の値は, 必ず入力チェック後に使用する. SC_FormParam を使用していれば, SC_FormParam::getHashArray() で取得できる

    <?php
    
    /** NG $_POST の値を入力チェックせずに使用する */
    $this->register($_POST);
    
    /** OK 必ず入力チェックを行う */
    $objFormParam = new SC_FormParam();
    $objFormParam->addParam('商品規格ID', 'product_class_id', INT_LEN, 'n', array('EXIST_CHECK', 'MAX_LENGTH_CHECK', 'NUM_CHECK'));
    $objFormParam->setParam($_POST);
    $objFormParam->convParam();
    $arrErr = $objFormParam->checkError()
    if (SC_Utils_Ex::isBlank($arrErr)) {
         // 入力チェック後の値が取得可能
         $arrForm = $objFormParam->getHashArray();
    }
    $this->register($arrForm);
    

    $_SESSION は, SC_CartSession クラス等, セッションを扱うビジネスロジックを通じて使用するのが望ましい.

    どうしても, グローバル変数を使用したい場合は, global キーワードは使用せず, $GLOBALS を使用すること.

    <?php
    /**
     * NG global キーワードの使用すると, 変数名衝突の原因となる
     */
    function globalSample() {
        global $conn;
    
        // 外部からは $conn という変数を使用しているのがわからない
        if (is_null($conn)) {
            $conn = new ClassName();
        }
    }
    
    /**
     * OK $GLOBALS を使用すると, ローカル変数との衝突の心配は無い
     */
    function globalSample() {
        if (is_null($GLOBALS['_conn'])) {
            $GLOBALS['_conn'] = new ClassName();
        }
    }
    

    変数名

    変数名で, どんなデータか識別できるよう考慮すること

    <?php
    
    /**
     * NG 一見して, どんなデータが格納されているかわからない
     */
    $arrRet = getProducts();
    
    /**
     * OK 商品データの配列だと, 変数名で判別可能
     */
    $arrProducts = getProducts();
    

    リテラルを格納する (つまり配列・オブジェクト以外の) 変数名は, すべて小文字を使用し, アンダーバーで区切ること

    <?php
    
    /**
     * リテラルを格納する変数名は, 連想配列の添字で使用されることも考慮し
     * アンダーバー区切りの方が扱いやすい
     */
    
    /**
     * NG
     */
    $productId = getProductId();
    
    /**
     * OK
     */
    $product_id = getProductId();
    

    1行の文字数/行数

    • 1行の文字数は80文字までにすることを目指すこと. すなわち, コードの長さを現実的な範囲で80文字までにおさめるよう努力すべきです. しかしながら, 場合によっては少々長くなってしまってもかまいません.
    • 1関数の行数は多くても200行程度に留めるのが望ましい. これ以上行数が増えてしまう場合は, 関数を分割する等, コーディングの見直しを行ってください

    PHPDoc コメント

    関数の PHPDoc コメントは必ず記述する.

    • 関数の説明
    • 引数
      • 引数の無い関数は省略可能
    • 返り値
      • 返り値が無くても void を明記すること

    非推奨機能

    error_reporting(E_ALL) で, エラー及び警告が出力されないようコーディングすること.

    PHP5.3 で非推奨となっている関数は使用しないこと.

    • call_user_method() (かわりに call_user_func() を使用します)
    • call_user_method_array() (かわりに call_user_func_array() を使用します)
    • define_syslog_variables()
    • dl()
    • ereg() (かわりに preg_match() を使用します)
    • ereg_replace() (かわりに preg_replace() を使用します)
    • eregi() (かわりに preg_match() で 'i' 修正子を使用します)
    • eregi_replace() (かわりに preg_replace() で 'i' 修正子を使用します)
    • set_magic_quotes_runtime() およびそのエイリアスである magic_quotes_runtime()
    • session_register() (かわりにスーパーグローバル $_SESSION を使用します)
    • session_unregister() (かわりにスーパーグローバル $_SESSION を使用します)
    • session_is_registered() (かわりにスーパーグローバル $_SESSION を使用します)
    • set_socket_blocking() (かわりに stream_set_blocking() を使用します)
    • split() (かわりに preg_split() を使用します)
    • spliti() (かわりに preg_split() で 'i' 修正子を使用します)
    • sql_regcase()
    • is_dst を mktime() に渡すこと。 かわりにタイムゾーン処理用の新しい関数を使用します。

    以下の機能も, PHP5.3.x で非推奨となっているが, PHP4互換のために使用しても良い EC-CUBE 2.12.0 から PHP4 は非対応となりました.

    • new の返り値を参照で代入すること
    • 呼び出し時の参照渡し

    参考 http://www.php.net/manual/ja/migration53.deprecated.php

    URL のファイルパス部の取得は $_SERVER['PHP_SELF'] を使わず、代わりに $_SERVER['SCRIPT_NAME'] を使用する。(参考: #1717)

    Facebook [en]Comment 

  • Q: [EC-CUBE 2系] EC-CUBE標準規約 TheVOS 2019.09.28
    A:

    基本的に Zend Framework PHP 標準コーディング規約 に順ずる.
    以下, 要点及び相違点を規定する.

    また, コーディングに際して, 以下のガイドラインに沿うことが望ましい

    EC-CUBE標準規約 > リファクタリングガイドライン

    PHPUnitを利用した単体テストを行う際は以下のガイドラインに沿う事が望ましい

    EC-CUBE標準規約 > 単体テストガイドライン

    命名規約

    ファイル名

    • 拡張子は, 各ファイル形式に準ずる.
      • PHPファイルは, 必ず .php を使用する.
    • PHPクラスは, 特別な場合を除き, 1クラス1ファイルとし, クラス名.php とする.

    PHPクラス名

    • 区切り文字としてはアンダースコア(_)を使用する.
    • クラス名称の先頭には, 大文字でその種類を表す Prefix を付加する.
      Prefix 種類
      SC, GC 1つのサイト内で共有するクラス (他のクラスから呼ばれる) SC_Customer.php
      LC 1つのソースファイル内で使用するクラス (通常他のクラスから呼ばれない) LC_Page_Abouts.php
    • クラスがパッケージに属する場合は, Prefix の後にパッケージ名を付加する.
      • Page パッケージでインデックスページとしてアクセスされるクラス名は Index とせず, 属する階層名をクラス名とする.
    • ユーザーが拡張するために extends するクラスは, クラス名の最後に Ex を付加する.

    関数名

    • 関数名の先頭には, 小文字でその種類を表す Prefix を付加する.
      • クラス名: LC_*
        • private または protected を意図する場合、Prefix「lf」を省略できる。(Prefix の付加がない場合、「lf」相当とみなす。)
        • public を意図する場合、Prefix「sf」を省略できない。
      • クラス名: SC_*, GC_*
        • public を意図するメソッドの場合、Prefix「sf」を省略できる。(Prefix の付加がない場合、「sf」相当とみなす。)
        • private または protected を意図する場合、Prefix「lf」を省略できない。
    • 名称が複数の単語からなる場合, それぞれの単語の先頭を大文字にする.
    • 関数名は, Prefix + 動詞 + 対象 を原則とする.
      Prefix 種類
      sf 一つのサイト内で共有する関数 sfGetProductName()
      lf 一つのソースファイル内で使用する関数 lfGetProductName()
      fn JavaScript で宣言された関数 fnGetProductName()

    変数名(Smarty 変数も含む)

    • 変数名の先頭には, 小文字でその種類を表す Prefix を付加する.
      • ループ等で一時的に使用する, 数値型の変数には慣習的な $i, $j, $k を使用しても良い.
    • リテラルを格納する (つまり配列・オブジェクト以外の) 変数名は, すべて小文字を使用し, アンダーバーで区切る.
    • 配列・オブジェクトの変数名はキャメルケースを使用する.
      Prefix 種類
      obj クラス変数(オブジェクト) $objQuery
      arr 配列 $arrCustmers
      tpl テンプレート変数(リテラル) $this->tpl_csv_id

    定数名

    • すべて大文字で宣言する.
    • 区切り文字としてアンダースコア(_)を使用する.
    • パスに関わるもの(パラメータを含む)は、下記を適用する。(#834 から展開)
      • *_URL: URL
        • 先頭は「スキーム名:」
      • *_URLPATH: URL における url-path 相当 (絶対パス)
        • 先頭は「/」
      • *_HTMLPATH: URL 上の EC-CUBE の /html/ からの相対パス
        • 先頭に「/」を含まない。(/html/ と同一の場合、空文字)
        • 命名に改善余地あり。 (参考: concrete5=DIR_REL) 現在は利用箇所が無いが、今後利用する必要も考えられるので再考していきたい。
      • *_REALFILE: (サーバの)ファイルシステム上のファイルの絶対パス
        • 先頭は「/」
      • *_REALDIR: (サーバの)ファイルシステム上のディレクトリの絶対パス
        • 先頭は「/」。末尾は「/」。
      • *_DIR: 上記に該当しない断片的なディレクトリ情報。
        • 先頭に「/」を含まない。
      • *_FILE: 上記に該当しない断片的なファイル情報。
      • *_PATH: 上記に該当しないもの。上記の複数に該当するもの。

    DBテーブル名

    • テーブル名の先頭には, 小文字でその種類を表す Prefix を付加する.
    • 区切り文字としてアンダースコア(_)を使用する.
    • Prefix 種類
      mtb マスタデータ mtb_pref
      dtb データテーブル dtb_order_detail

    DBカラム名

    • 特に指定の無い限り, すべて小文字を使用する.
    • 区切り文字としてアンダースコア(_)を使用する.

    CSS クラス名

    • 特に定義なし

    コーディング規約

    HTML

    • ラジオボタン, チェックボックスは <label></label> で囲み, 文字をクリックしてもチェックされるようにする.

    PHPコード

    • 改行コードは, 基本的に LF を使用する.
      • 使用する Subversion クライアントによっては, svn add を実行した際に, OS 元来の改行コードがリポジトリに反映されてしまう場合があるため, svn propset で svn:eol-style=LF を設定しておくのが望ましい.
    • PHPコードの開始タグと, 終了タグは以下のように記述する.
    • 終了タグの後に必ず LF (UNIX の改行コード)を1つ入れる.
    • 余分な空行, 行末の空白は極力削除する.
    • クラス定義, 関数定義の後, 括弧の後で改行する.
    • 80 文字を目安に改行する.
      <?php
      class LC_Page_Abouts
      {
      
          function process()
          {
              // some logic.
          }
      }
      

    制御文

    • if, for, while, switch 等の制御構造は, 次のルールで開き括弧で記述する.
    • インデントは, 半角スペース4文字を使用し, タブは使用しない.
    • if 文
      <?php
      if ($flag == '1') { // '1' == $flag と書くのは NG
          $ret = 1;
      } elseif ($flag == '2') {
          $ret = 2;
      } else {
          $ret = 3;
      }
      
      • 判定文の対象となる処理結果は先に記述する.
    • for 文
      <?php
      for ($i = 0; $i < $max; $i++) {
          echo $i . "\n";
      }
      
    • foreach 文
      <?php
      foreach ($var as $key => $val) {
          echo $key . ':' . $val . "\n";
      }
      
    • while 文
      <?php
      while ($flag) {
          var_dump($flag);
      }
      
    • do while 文
      <?php
      do {
          var_dump($flag);
      } while ($flag);
      
    • switch 文
      <?php
      switch ($var) {
          case VAR_ONE:
              echo 'one';
              break;
      
          case VAR_TWO:
              echo 'two';
              break;
      
          default:
              echo 'default';
              break;
      }
      
      • case の記述は, 定数を用いるのが望ましい.

    文字列

    • 長くなる文字列は分割し, "." で結合する.
      <?php
      $sql = 'SELECT name, age, birthday, zipcode, address, comment '
           . '  FROM user_information '
           . ' WHERE user_id = ? '
           . '   AND is_delete = 0 ';
      
    • ヒアドキュメントを使用しても良い
      <?php
      $sql = <<< __EOS__
          SELECT name,
                 age,
                 birthday,
                 zipcode,
                 address,
                 comment
            FROM user_information
           WHERE user_id = ?
             AND is_delete = 0
      __EOS__;
      

    SQL文

    • フォームから入力された値を利用して SQL文を生成する場合, SQLインジェクションを防ぐため, 必ず PEAR::MDB2 のブレースホルダを利用する.

    コメント

    • コメントのコーディングは基本的に phpDocumentor に準ずる.

    ヘッダ

    • 各ファイルのヘッダに著作権表記を記述する.
      <?php
      /*
       * This file is part of EC-CUBE
       *
       * Copyright(c) 2000-2011 LOCKON CO.,LTD. All Rights Reserved.
       *
       * http://www.lockon.co.jp/
       *
       * This program is free software; you can redistribute it and/or
       * modify it under the terms of the GNU General Public License
       * as published by the Free Software Foundation; either version 2
       * of the License, or (at your option) any later version.
       *
       * This program is distributed in the hope that it will be useful,
       * but WITHOUT ANY WARRANTY; without even the implied warranty of
       * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       * GNU General Public License for more details.
       *
       * You should have received a copy of the GNU General Public License
       * along with this program; if not, write to the Free Software
       * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
       */
      

    クラス定義

    • phpDoc コメントは必要に応じて記述する.
    • @version は $Id$ を使用する.
    • メンバ変数は / */ を使用することによって phpDoc コメントとして認識される.
      <?php
      /**
       * クラスの簡単な説明
       *
       * クラスの詳細な説明....
       * ...
       *
       * @package Page
       * @author LOCKON CO.,LTD.
       * @version $Id$
       */
      class LC_Page
      {
      
          /** メンバ変数 */
          var foo;
      
      }
      

    Pageクラス

    • class/pages以下において exit; を個別の処理でしない。exit処理はすべてSC_Response_Ex::actionExit(); に共通化すること.

    関数定義

    • phpDoc コメントは必要に応じて記述する.
      • @param と @return は必須
    • コード文中にも必要に応じて的確なコメントを記述する.
      <?php
      /**
       * 関数の簡単な説明.
       *
       * 関数の詳細な説明....
       * ....
       *
       * @access private
       * @param string $foo 引数の説明
       * @param string|integer $bar 引数の説明
       * @return string 返り値の説明
       */
      function process($foo, $bar = '')
      {
          // some process...
          return 'string value';    
      }
      

    その他

    • 必要に応じて, 下記のタスクタグを使用しても良い
      • TODO - TODO として残したいコメント
      • FIXME - 必ず修正することを促すコメント
      • XXX - 動くけど怪しい...
        <?php
        // TODO リファクタリングすること.
        
        // FIXME 要修正. バグの説明(#135)
        
        /**
         * :XXX: 以下, 怪しいロジック(#999)
         * 複数行のコメントはこのように.
         *
         * 必要に応じて チケットの ID も記述する.
         */
        
    • コメントによってソースコードが見難くならないように注意する.

    Facebook [en]Comment 

  • Q: [EC-CUBE 3.x] デフォルトのtitleを任意のものに変更する方法 TheVOS 2019.04.10
    A:
    EC-CUBE 3.X:デフォルトのtitleを任意のものに変更する方法

    EC-CUBE 3.Xでデフォルトでtitle部分に表示されるものを任意で削除・変更したり、あらかじめ用意されているページのページ名(テンプレート名)を任意のものに変更する方法です。

    ※紹介している内容はEC-CUBEのVersion 3.0.10で動作確認したもので、デフォルトのテンプレートを使っている想定になります。

    ※変更した内容が反映されない場合は、管理画面の「コンテンツ管理 < キャッシュ管理」にある「キャッシュクリア」ボタンを押して再度確認してみてください。

    「TOPページ」の表記を消す

    デフォルトのテンプレートをそのまま使用している場合、TOPページのtitleは「ショップ名 / TOPページ」という表示になっています。
    これはこれで今見てるのがTOPページだとわかりやすいのですが、個人的にはわざわざTOPページという表記は必要ないと思うので消してみます。

    まず、デフォルトテンプレートではtitle部分を表示している記述はdefault_frame.twigというファイルになるので、その中にある下記のような記述(27行目辺り)を探します。

    default_frame.twig

    <title>{{ BaseInfo.shop_name }}{% if subtitle is defined and subtitle is not empty %} / {{ subtitle }}{% elseif title is defined and title is not empty %} / {{ title }}{% endif %}</title>
    

    上記記述を見つけたら、ここに条件分岐を追記していきます。
    具体的にはtitleの中にTOPページの場合にという条件文を記述(コードハイライト部分)し、TOPページにはショップ名のみを表示させ、その他のページにはデフォルトで使われているショップ名とページ名が表示されるようにします。

    default_frame.twig

    <title>
    {% if PageLayout.url == "homepage" %}
      {{ BaseInfo.shop_name }}
    {% else %}
      {{ BaseInfo.shop_name }}{% if subtitle is defined and subtitle is not empty %} / {{ subtitle }}{% elseif title is defined and title is not empty %} / {{ title }}{% endif %}
    {% endif %}
    </title>
    

    ※インデントと改行はソースがわかりやすいように入れているだけなので、必要なければ取り除いて問題ないです。

    上記変更後にページを確認(必要であればキャッシュクリアをしてから)すると、TOPページのtitleはショップ名のみが表示されているのを確認でき、他のページに関してはこれまで通りショップ名とページ名が表示されているのを確認できます。

    デフォルトのページ名を変更する

    これは自分がやり方を知らないだけの可能性もありますが、EC-CUBEは新たに自分で作成したページは任意のページ名をつけることができますが、例えば「当サイトについて」や「プライバシーポリシー」のように、あらかじめ用意されているものについては任意のページ名に変更することができません。
    上で紹介したように条件分岐を利用してテンプレート側で直接変更ということもできなくはないですが、その方法だと表側だけ任意のものに変更されて、管理画面のページ管理などでは結局元の名称のままでページ名(テンプレート名)が表示されてしまい微妙です...。

    これを表でtitleとして表示されるのはもちろん、管理画面でもちゃんと任意で付けたページ名で表示されるようにしたいという時にはデータベースの記述を変更することで可能になります。

    ※データベースは間違った個所を変更してしまったりすると最悪の場合元に戻せないなど起こりうるので、不安な人はあらかじめバックアップをとるなどして作業してください。

    ここでは「当サイトについて」というページ名を「会社概要」というページ名に変更する形で紹介します。

    まず、phpMyAdminなどでデータベースにアクセスして自身の環境でEC-CUBEで利用しているデータベースの情報を開き、その中にある「dtb_page_layout」というテーブルを表示します。
    テーブルの詳細画面が表示されるとデフォルトであればズラッと各ページの情報や項目が表示されると思うので、その中にある「page_name」の項目を探して、且つ「当サイトについて」と記述されている部分を探します。
    あとは、一覧画面で直接もしくは更に詳細ページに移動してから「当サイトについて」と表示されているところを「会社概要」に変更すれば、表側のtitleや管理画面のページ管理などのページ名表示が変更されているのを確認できます。

    今回は例として「当サイトについて」を変更する形で紹介しましたが、同じようにすれば例えば「プライバシーポリシー」を「個人情報保護方針」にするだとか、「MYページ」という表記はすべて「マイページ」といったカタカナ表記のみにしたいといったことが可能です。

    Facebook [en]Comment 

  • Q: [EC-CUBE 3.x] titleの並びや区切り記号を変更する方法 TheVOS 2019.04.10
    A:
    EC-CUBE 3.X:titleの並びや区切り記号を変更する方法

    EC-CUBE 3.Xは、デフォルトのテンプレートだとtitle表示が「ショップ名 / ページ名」という形になっていますが、それを任意の並びにしたり区切り記号を変更したりする方法です。

    ※紹介している内容はEC-CUBEのVersion 3.0.10で動作確認したもので、デフォルトのテンプレートを使っている想定になります。

    ※変更した内容が反映されない場合は、管理画面の「コンテンツ管理 < キャッシュ管理」にある「キャッシュクリア」ボタンを押して再度確認してみてください。

    公式サイトで公開されているデモを見ても確認できますが、例えばデフォルトだとtitleの表示がTOPページは「ショップ名 / TOPページ」、当サイトについてページでは「ショップ名 / 当サイトについて」といったように、先頭にショップ名が表示、区切り記号は「/」を使用、その後ページ名を表示という形になっています。
    このままでも気にならないという人ももちろんいるでしょうが、並びと区切り記号を変更したいという時は以下を変更することで任意の表示にできます。

    まず、デフォルトテンプレートではtitle部分を表示している記述はdefault_frame.twigというファイルになるので、その中にある下記のような記述(27行目辺り)を探します。

    default_frame.twig

    <title>{{ BaseInfo.shop_name }}{% if subtitle is defined and subtitle is not empty %} / {{ subtitle }}{% elseif title is defined and title is not empty %} / {{ title }}{% endif %}</title>
    

    今回は例として「ショップ名 / ページ名」の表示を「ページ名 - ショップ名」に変更してみます。
    上記の記述を下記のように変更し、{{ BaseInfo.shop_name }}{{ title }}といったショップ名やページタイトルをそれぞれ表示するタグの並びを入れ替え、区切り記号も「/」からサンプルコードのハイライト表示部分のように「-」を使う形に変更します。

    default_frame.twig

    <title>{% if subtitle is defined and subtitle is not empty %}{{ subtitle }} - {% elseif title is defined and title is not empty %}{{ title }} - {% endif %}{{ BaseInfo.shop_name }}</title>
    

    上記変更後にページを確認(必要であればキャッシュクリアをしてから)すると各ページのtitle部分が「ページ名 - ショップ名」という形に変更されているのを確認でき、例えばTOPページの場合は「TOPページ - ショップ名」へ、当サイトについてページであれば「当サイトについて - ショップ名」といった形になります。

    Facebook [en]Comment 

  • Q: [EC-CUBE 2.x] 商品数や階層に関係なく全カテゴリーを表示させる方法 TheVOS 2019.04.10
    A:
    EC-CUBE 2.X:商品数や階層に関係なく全カテゴリーを表示させる方法

    EC-CUBEの備忘録。
    デフォルトのテンプレートだとサイドに表示されている商品カテゴリーですが、商品登録がない場合はカテゴリー名が表示されず、商品が登録されていたとしても親カテゴリーのページでなければ子カテゴリー名は表示されないようになっています。
    これを商品数や表示ページに関わらず、すべてのカテゴリーを常に全表示させる方法です。

    ※EC-CUBEのバージョンはVersion 2.12.4で動作確認しています。

    カテゴリーが全て表示されない

    画像はデフォルトのテンプレートのトップページをキャプチャしたものです。
    現在表示されているのはサンプルとして予め登録されている「食品」と「レシピ」という2つのカテゴリーですが、実は表示されてないだけで「雑貨」というカテゴリーも予め登録されています。
    また「食品」というカテゴリーですが、こちらは予め「なべ」と「お菓子」という子カテゴリーが登録されています。

    実際存在しているカテゴリーなのに表示されないのは、「雑貨」の場合は商品が登録されていないため。
    「なべ」と「お菓子」の場合は、親となる「食品」ページではなく現在トップページを表示しているという理由で、それぞれカテゴリーが表示されないようになっています。
    これを常に全てのカテゴリーが表示されるようにする方法です。

    まず/data/Smarty/templates/default/frontparts/bloc内のcategory.tplを変更します。

    category.tpl

    <!--{if $arrTree[cnt].display == 1}-->
    

    37行目辺り(ライセンスコメント含め)に「表示フラグがTRUEなら表示」というコメントがあり、そのすぐ下に上記のような記述があると思います。
    これを下記のハイライト表示部分のようにor trueを追記したものにします。

    category.tpl

    <!--{if $arrTree[cnt].display == 1 or true}-->
    

    ここまで行い表示を確認すると、先ほどはトップページでは出ていなかった子カテゴリーも表示されるようになります。
    次に商品数がない場合でもカテゴリーが表示されるようにします。
    /data/class/pages/frontparts/bloc内のLC_Page_FrontParts_Bloc_Category.phpを変更します。

    LC_Page_FrontParts_Bloc_Category.php

    $this->arrTree = $this->lfGetCatTree($this->tpl_category_id, true);
    

    74行目辺り(ライセンスコメント含め)に「カテゴリツリーの取得」というコメントがあり、そのすぐ下に上記のような記述があると思います。
    これを以下のハイライト表示部分のように、trueとなっていたものをfalseに変更します。

    LC_Page_FrontParts_Bloc_Category.php

    $this->arrTree = $this->lfGetCatTree($this->tpl_category_id, false);
    
    カテゴリーが全て表示される

    上記それぞれを変更して表示確認すると、画像のように先ほどは表示されていなかったカテゴリーが全て表示されるようになっているのを確認できます。

    Facebook [en]Comment 

  • Q: [EC-CUBE 2.x] 商品名などで長くなったテキストを省略する方法 TheVOS 2019.04.10
    A:
    EC-CUBE 2.X:商品名などで長くなったテキストを省略する方法

    引き続きEC-CUBEの備忘録です。
    商品のタイトルや紹介文が指定した文字数以上になったときに「...」などを表示して、見栄えを揃える方法です。
    jQueryを使うとかCSSのtext-overflowなんかでも同じ事はできますが、Smartyを使って実装するものになります。

    ※EC-CUBEのバージョンはVersion 2.12.3になります。

    一部の商品の文字が長くて、バランスが悪くなる...

    画像はデフォルトのテンプレートで登録されているサンプル商品の「アイスクリーム」のタイトルと説明文を長くしたものです。
    見栄えを気にしないのであればこのままでも良いですが、他の商品とのバランスも考えてタイトル、商品説明文をそれぞれ2行で収まるぐらいの文字数になるよう設定し、更に省略された部分は「…」を表示させるようにします。

    今回サンプルとして変更するこの部分は「おすすめ商品」というブロックで表示されているので、管理画面の「デザイン管理 → PC → ブロック設定 → おすすめ商品」からテンプレートを変更します。
    FTPなどの場合は/data/Smarty/templates/default/frontparts/bloc/内のrecommend.tplが該当ファイルになります。

    recommend.tpl

    <div class="productContents">
      <h3>
        <a href="<!--{$smarty.const.P_DETAIL_URLPATH}--><!--{$arrProduct.product_id|u}-->"><!--{$arrProduct.name|h}--></a>
      </h3>
      <p class="sale_price">
        <!--{$smarty.const.SALE_PRICE_TITLE}-->(税込): <span class="price"><!--{$arrProduct.price02_min_inctax|number_format}--> 円</span>
      </p>
      <p class="mini comment"><!--{$arrProduct.comment|h|nl2br}--></p>
    </div>
    

    上記はコードの一部を抜き出したもので、h3がタイトルを出力している部分、p class="mini comment"が紹介文を出力している部分になります。
    これをそれぞれ下記のように変更します。

    recommend.tpl

    <div class="productContents">
      <h3>
        <a href="<!--{$smarty.const.P_DETAIL_URLPATH}--><!--{$arrProduct.product_id|u}-->"><!--{$arrProduct.name|mb_substr:0:20|h}--><!--{if $arrProduct.name|mb_strlen > 20}-->...<!--{/if}--></a>
      </h3>
      <p class="sale_price">
        <!--{$smarty.const.SALE_PRICE_TITLE}-->(税込): <span class="price"><!--{$arrProduct.price02_min_inctax|number_format}--> 円</span>
      </p>
      <p class="mini comment"><!--{$arrProduct.comment|mb_substr:0:30|h|nl2br}--><!--{if $arrProduct.comment|mb_strlen > 30}-->...<!--{/if}--></p>
    </div>

    上記コードに変更(ハイライト部分が変更箇所)することで、タイトルは最大文字数が20文字でそれ以上は「…」を表示、商品説明は最大文字数が30文字でそれ以上は「…」が表示されるようになり、見栄えを確認すると以下のようになります。

    長すぎるテキストを省略して、「...」を表示

    文字数を変更する場合はそれぞれ数値を任意のものに変更し、省略時に表示したいテキストもifの部分で変更ができます。
    今回はトップページに表示されている「おすすめ商品」をサンプルにしましたが、Smartyであれば商品一覧などの他のテンプレートでも実装できます。

    Facebook [en]Comment 

  • Q: [EC-CUBE 2.x] サイトデザインをPCで統一させる方法 TheVOS 2019.04.10
    A:
    EC-CUBE 2.X:サイトデザインをPCで統一させる方法

    EC-CUBEはもともとスマートフォンやモバイル用のテンプレートが用意されており、それぞれのデバイスで閲覧すると各テンプレートに振り分けらるようになっているのですが、それを無効にする方法です。
    この機能自体は良いと思うのですが、例えばモバイルまで手が回らないとか、単純に別テンプレートにする必要がないというときに。

    ※EC-CUBEのバージョンはVersion 2.12.3になります。

    iPhoneでの閲覧時

    画像はiPhoneでのサイト閲覧時をキャプチャしたもので、このように専用のテンプレートで表示されています。
    スマートフォンっぽい感じなのは良いですが、PCのデザインの雰囲気とかなり離れている場合は微妙です...。
    また、デフォルトでは思いっきりEC-CUBEのロゴが表示されていたりもします。(ここらへんはちゃちゃっとスマートフォン用のテンプレートを直せば良い話ですが)

    一番好ましいのはもちろんスマートフォンやモバイルのテンプレートもしっかりカスタマイズするのが好ましいですが、納期や予算の問題などでしない(できない)場合は、いっそのこと全てPC用のテンプレートにするのも手だと思います。

    今回は手っ取り早くできる方法で、具体的には予め設定されているユーザーエージェントを切るという方法になります。
    /data/class/内にあるSC_Display.phpというファイルの133行目あたりから下記のような記述があります。

    SC_Display.php

    /**
     * 端末種別を判別する。
     *
     * SC_Display::MOBILE = ガラケー = 1
     * SC_Display::SMARTPHONE = スマホ = 2
     * SC_Display::PC = PC = 10
     *
     * @static
     * @param   $reset  boolean
     * @return integer 端末種別ID
     */
    public static function detectDevice($reset = FALSE) {
      if (is_null(SC_Display_Ex::$device) || $reset) {
        $nu = new Net_UserAgent_Mobile();
        $su = new SC_SmartphoneUserAgent_Ex();
        if ($nu->isMobile()) {
          SC_Display_Ex::$device = DEVICE_TYPE_MOBILE;
        } elseif ($su->isSmartphone()) {
          SC_Display_Ex::$device = DEVICE_TYPE_SMARTPHONE;
        } else {
          SC_Display_Ex::$device = DEVICE_TYPE_PC;
        }
      }
      return SC_Display_Ex::$device;
    }
    

    上記コードの一部を下記のように一部コメントアウトし、ユーザーエージェントを無効にします。
    これにより全てのデバイスで閲覧時にはPCのデザインで表示されるようになります。

    SC_Display.php

    /**
     * 端末種別を判別する。
     *
     * SC_Display::MOBILE = ガラケー = 1
     * SC_Display::SMARTPHONE = スマホ = 2
     * SC_Display::PC = PC = 10
     *
     * @static
     * @param   $reset  boolean
     * @return integer 端末種別ID
     */
    public static function detectDevice($reset = FALSE) {
      if (is_null(SC_Display_Ex::$device) || $reset) {
        $nu = new Net_UserAgent_Mobile();
        $su = new SC_SmartphoneUserAgent_Ex();
    //    if ($nu->isMobile()) {
    //      SC_Display_Ex::$device = DEVICE_TYPE_MOBILE;
    //    } elseif ($su->isSmartphone()) {
    //      SC_Display_Ex::$device = DEVICE_TYPE_SMARTPHONE;
    //    } else {
            SC_Display_Ex::$device = DEVICE_TYPE_PC;
    //    }
      }
      return SC_Display_Ex::$device;
    }
    

    変更・アップロード後に再度スマートフォンやモバイルからアクセスすれば、PCのテンプレートで表示されるのが確認できます。
    上記ではスマートフォンとモバイルの両方のユーザーエージェントを無効にしていますが、モバイルはそのまま専用テンプレートで表示したいという場合は、スマートフォン部分のみ(DEVICE_TYPE_SMARTPHONE)をコメントアウトするなどしてください。

    ちなみに、以下のようにして単純に条件分岐の記述を取っ払って全てDEVICE_TYPE_PCにする方法でもいけます。
    ただ、確実に今後必要でない場合などはこの方法で良いですが、後々テンプレートを適応させる可能性が少しでもあるのであれば先程のコメントアウトを用いたほうがいいかと思います。

    SC_Display.php

    /**
     * 端末種別を判別する。
     *
     * SC_Display::MOBILE = ガラケー = 1
     * SC_Display::SMARTPHONE = スマホ = 2
     * SC_Display::PC = PC = 10
     *
     * @static
     * @param   $reset  boolean
     * @return integer 端末種別ID
     */
    public static function detectDevice($reset = FALSE) {
      return DEVICE_TYPE_PC;
    }
    

    Facebook [en]Comment 

  • Q: [EC-CUBE 2.x] 商品ステータスを変更・追加する方法 TheVOS 2019.04.10
    A:
    EC-CUBE 2.X:商品ステータスを変更・追加する方法

    EC-CUBE自体をまだあまり触ったこともなければ、そんなに頻繁に使わないのもあって、しばらくすると簡単なことでもいろいろと忘れてしまうので備忘録。
    デフォルトだと「NEW」とか「残りわずか」などで登録されている商品ステータスを任意のものに変更・追加する方法です。

    ※EC-CUBEのバージョンはVersion 2.12.3になります。

    デフォルトの商品詳細ページ

    画像はEC-CUBEのデフォルトテンプレートの商品詳細ページです。
    現在「NEW」「残りわずか」「ポイント2倍」「オススメ」「限定品」とアイコンが出ているこの部分のテキストや画像を任意のものに変更します。

    商品ステータスのテキストを変更する

    テキストと画像のどちらを先に変えてももちろん問題ないですが、今回はテキストから変更します。
    デフォルトの画像を変えるだけの場合は、ここの部分は飛ばしてください。

    EC-CUBEの管理画面にログイン後、メニューにある「システム設定 → マスターデータ管理」を選択します。
    マスターデータ管理ページにいくと、プルダウンがあるので「mtb_status」を選択します。
    選択後にプルダウン横の「選択」ボタンを押すと下記のようなページになります。

    マスターデータ管理のmtb_status選択時

    見たまま、もともと登録されているものを変更したい場合は、IDと値の部分をそれぞれ変更します。
    これ以外で新たに登録したい場合は、その下にある「追加のデータ」部分に任意でIDと値(テキスト)を設定します。
    今回はサンプルとして「ID:6 値:テスト」を追加します。
    設定が終わったら、一番下にある「この内容で登録する」ボタンで商品ステータスのテキストが変更できます。
    変更後は、管理画面の商品登録ページなどにも商品ステータスの変更や追加がされています。

    商品ステータスの画像を変更する

    EC-CUBEの管理画面にログイン後、メニューにある「システム設定 → マスターデータ管理」を選択します。
    マスターデータ管理ページにいくと、プルダウンがあるので「mtb_status_image」を選択します。
    選択後にプルダウン横の「選択」ボタンを押すと下記のようなページになります。

    マスターデータ管理のmtb_status_image選択時

    テキスト変更のときと同様で、もともと登録されているものを変更したい場合はIDと値の部分をそれぞれ変更します。
    これ以外で新たに登録したい場合は、その下にある「追加のデータ」部分に任意でIDと値(画像へのパス)を設定します。
    今回はサンプルとしてID:1で登録されている「NEW」の画像をデフォルトから任意のものへ変更し、先程新たに追加した「テスト」にも画像を設定します。

    まずID:1の「NEW」アイコンの画像を変更します。
    値でimg/icon/ico_01.gifとなっている部分をimg/icon/ico_new.gifへ変更します。
    次に新たに追加した「テスト」にもアイコンを設定します。
    IDには先程登録した6を記述し、値にはimg/icon/ico_test.gifというように画像へのパスを記述します。
    設定が終わったら、一番下にある「この内容で登録する」ボタンで商品ステータスの画像が変更できます。

    ※IDは「mtb_status」に対応したものを記述してください。

    ※画像のパスは環境によって変更してください。

    ※デフォルトのテンプレート(Version 2.12.3)でテキスト名や画像名はそのままで、画像のみを差し替えたい場合は/html/user_data/packages/default/img/icon内に画像があるので、ここで同名のファイル名で上書けば変更されます。
    また、新たにアップする画像もデフォルトのテンプレートの場合にはここに画像を入れます。

    ※画像サイズが変わる場合は、各テンプレート(detail.tpllist.tplなど)で指定されているwidthheightも変更する必要があります。

    商品ステータス変更後の商品詳細ページ

    画像は商品ステータス変更後の商品詳細ページで、「NEW」の画像がオレンジのものから赤へと変更されており、「TEST」というアイコンが新たに追加されています。

    Facebook [en]Comment 

  • Q: [EC-CUBE 3.x] 新規作成したページURLから「user_data」を消す方法とURLを出力するテンプレートタグ TheVOS 2019.04.10
    A:
    EC-CUBE 3.X:新規作成したページURLから「user_data」を消す方法とURLを出力するテンプレートタグ

    EC-CUBE 3.Xではもともと用意されているページを利用するだけでなくオリジナルで新規ページを追加することもできるのですが、その場合作成したページのURLに「user_data」が付与されています。
    この「user_data」という部分をURLから消す方法とそれに関連して新規作成したページのURLをテンプレートタグを使って出力する方法です。

    ※紹介している内容はEC-CUBEのVersion 3.0.10で動作確認したもので、デフォルトのテンプレートを使っている想定になります。

    新規作成したページURLから「user_data」を消す

    EC-CUBEは例えばプライバシーポリシーや特定商取引法に基づく表記などのようにECサイトであれば必要なページはある程度最初から用意されており、さらに表示させる要素やスタイル調整などもある程度できるようにはなっていますが、それらを利用する以外にもオリジナルのページを新たに作成できるようにもなっています。

    ただ、オリジナルページはコンテンツをはじめ、名称・URL・ファイル名なども自由に決めることができるようになってはいるのですが、デフォルトではURLに「user_data」というのが付与されます。
    特にこだわり等もなければそのままでいいですが、できれば消したいという要望がほとんどなので、この「user_data」を消してみます。
    URLから「user_data」を消す方法としては、ファイル移動・DB操作・.htaccessを利用などのいろいろと方法はあるのですが、ここではルーティングを変更する方法を紹介します。

    まずsrc/Eccube/ControllerProviderにあるFrontControllerProvider.phpを開き、41行目に辺りにある下記の記述を見つけます。

    FrontControllerProvider.php

    // user定義
    $c->match('/'.$app['config']['user_data_route'].'/{route}', '\Eccube\Controller\UserDataController::index')->assert('route', '[0-9a-zA-Z_]+')->bind('user_data');
    

    上記記述を見つけたら、$c->match('/'.$app['config'] ~の部分を削除もしくはコメントアウトして、その代わりに下記を記述します。

    FrontControllerProvider.php

    // user定義
    $c->match('/' . '/{route}', '\Eccube\Controller\UserDataController::index')->assert('route', '[0-9a-zA-Z_]+')->bind('user_data');
    

    上記を記述後に作成したページを確認すると、例えば「example」というURLで作成したページの場合はデフォルトでは「http://example.com/html/user_data/example」のようになっていますが、それが「http://example.com/html/example」というように「user_data」がないURLになっているのを確認できます。

    この方法は以下EC-CUBEのフォーラムで紹介されていた方法です。

    URLを出力するテンプレートタグ

    a要素でリンクを設定する際など、例えばお問い合わせページであれば「http://example.com/contact」とか「/contact」のような記述で設定するのでも一応ページ遷移もできはするのですが、こういったCMSなどはURLを出力するテンプレートタグなどが用意されていることがほとんどですし、それらを利用していなかったことで後々大変になることもあったりするので、基本的にはそちらを利用するべきです。

    EC-CUBEでURLを出力するテンプレートはそれぞれ下記のような記述になり、下記は上からTOPページ・お問い合わせ・商品一覧のURLを出力させるものになります。
    基本的には赤文字部分の中をページ毎に変更し、デフォルトのテンプレートとして用意されている商品一覧やプライバシーポリシーのようにURLの中にディレクトリが複数あるような場合は_を利用して記述します。

    <!-- TOPページ -->
    <a href="{{ url('homepage') }}">TOP</a>
    
    <!-- お問い合わせ -->
    <a href="{{ url('contact') }}">お問い合わせ</a>
    
    <!-- 商品一覧 -->
    <a href="{{ url('product_list') }}">商品一覧</a>
    

    上の流れで行くと新規作成したページも{{ url('xxx') }}と記述して「xxx」の部分を作成した際に決めたURLにすれば問題ないように感じますが、実はこれだと意図した形には出力されず、例えば「example」というURLにしたからといって{{ url('example') }}と記述してもリンク先は404となってしまいます。
    新規作成したページのURLを出力したい時はこれまでとは少し違った記述方法になり、例えば「example」というURLにしたページのURLを出力するときは下記のように記述をします。

    <a href="{{ url(app.config.user_data_route, {'route': 'example'}) }}">サンプル</a>
    

    Facebook [en]Comment 

  • Q: [Mail service] 메일 사용자 설명서 TheVOS 2019.03.29
    A:
    원 포인트 가이드

    메일 소프트에 다음 정보를 설정하십시오.

    메일 서버 (POP)
    sv11.star.ne.jp
    메일 서버 (SMTP)
    sv11.star.ne.jp
    계정 (사용자 이름)
    만든 계정 이름 (@이후도 포함)
    비밀번호
    설정 한 패스워드

    Facebook [en]Comment 

  • Q: [$user_lang->userLang20181212221921012] 페이스북위젯 삽입하기 TheVOS 2018.12.15
    A:

    페이스북 위젯을 삽입하는 방법을 알아보겠습니다.

     

    1. 페이스북 위젯을 설정하는 곳으로 갑니다. ( https://developers.facebook.com/docs/plugins/page-plugin )

     

    2. 원하는 위젯모양을 만듭니다. 밑의 이미지를 보시면 쉽게 이해가 되실꺼예요.

     

    1.jpg

     

    3. 설정이 끝나면 하단 Get Code 를 누릅니다.

     

    4. IFRAME 코드를 복사하여 원하시는 곳에 삽입하면 완료!! 

     

    2.jpg

     


    Facebook [en]Comment 

  • Q: [$user_lang->userLang20181212221921012] 구글 캘린더 합치기 TheVOS 2018.12.12
    A:

    구글 캘린더로 일정관리를 하는 사람이라면 한번쯤 여러개로 나눠진 캘린더를 하나로 합쳐야 할 때가 생깁니다.

    저같은 경우는 처음에 일정관리에 심취해서 캘린더 수를 아주 자세하게 여러개로 나눠 놨었죠.

    그러나 최근에서야 단순함의 미학과 효율성에 눈을 뜨게 되었고, 10개가량 되던 캘린더들을 합쳤습니다. 딱 세개로.

    자 그럼,


    구글 캘린더 합치는 방법을 알아 봅시다.


    STEP1 캘린더 내보내기, Export


    1) 내보내고 싶은 캘린더 우측 삼각형 메뉴를 내려서 "Calendar Settings" 클릭


    2) 캘린더설정에서 Export Calendar 클릭하면,



    3) 캘린더이름_알수없는문자들@group.calendar.google.com.ics 라는 파일이 생성됩니다.



    STEP2 캘린더 읽어오기, Import


    1) 캘린더설정에서 Import Calendar 클릭하고,

    2) ics파일을 선택,

    3) Import 클릭하면


    4) 이벤트 갯수만큼 읽어오기 성공했다는 문구가 뜨면 끝.



    STEP3 캘린더 지우기, Unsubscribe


    1) 캘린더설정에서 Unsubscribe 클릭하면 없애고 싶은 캘린더가 사라집니다.

    이 없앤 캘린더를 복구하는 방법도 있습니다만 이 글에서는 다루지 않도록 할게요.



    여기까지 구글 캘린더 합치는 방법을 알아 봤습니다.

    Facebook [en]Comment 

  • Q: [Manual] THEVOS Youtube BOARD SKIN 사용 설명 TheVOS 2018.11.11
    A:

    [ 스킨 기능 ]

    - 사용자 정의 자동추가

      게시판 스킨을 "INSP 유튜브"으로 변경하고 사용자정의 eid(dex_embed_srl)가 없을겨우 자동으로 추가됨


    - 기존게시판에서도 사용가능

      ㄴ 게시판에 필요한 사용자 정의는 "thevos_extv_youtube_code" 이다.

      ㄴ 하지만 skin설정에서 동영상설정 -> 영상주소eid 에서 해당 값을 바꿀수 있다.

      ㄴ 기존 게시판에 사용자 정의가 있을경우에는 제일 마지막에 사용자 정의가 추가된다.


    - 동영상 출력

      글쓰기페이지에서 동영상입력박스에다가 유튜브의 주소 https://youtu.be/vOyAbCT3GBw 또는 vOyAbCT3GBw 이라고 입력할경우 게시판 뷰페이지 상단에 동영상이 뜨게됨


    - 동영상 사이즈

      별도로 지정하지 않으며 컨텐츠의 가로 폭만큼 유동적으로 늘어남 반응형 대음


    - 리스트 썸네일 유튜브 썸네일로 출력

      ㄴ 리스트 썸네일은 유튜브 썸네일로 출력한다.

      ㄴ 단! 사용자가 첨부파일에 이미지를 업로드하고 대표이미지로 설정할경우.. 해당 이미지가 리스트에 출력하게된다

          첨부파일에 이미지를 업로드하고 대표이미지를 설정하지 않을경우에는 그냥 유튜브 썸네일이 출력된다.


    - 반응형

      ㄴ PC, 태블릿, 모바일 등 모든 기기에 대응

    Facebook [en]Comment 

Board Pagination Prev 1 2 Next
/ 2