메뉴얼

  • Q: [EC-CUBE 2系] EC-CUBE 2系でポート番号付で管理画面を動かす 2020.01.02
    A:

    Apacheの処理分散させるために、ロードバランサーを介してラウンドロビンを掛けた複数台で同じEC-CUBEを動かす必要が出てきました。
    Apacheを動作させるフロントエンドをコピーした複数台でPHP処理を分担、MySQLはローカル接続されているバックエンドの専用サーバーで運用という形です。

    EC-CUBEはそもそもPHPセッションをDB保存しているので、DBをバックエンドのサーバーで共通化しておけば複数台でのPHPセッションの共有化を配慮する必要はなく、複数台運用への切り替えは簡単です。

    ただし、管理画面からアップロードした画像等はApacheを動かしているサーバーに物理的に保存されるため、ラウンドロビンが掛かった状態ではどのサーバーにファイル保存されるか分からなくバラバラになってしまうため、どれか1台固定のサーバーを正としてそこへ保存し、rsync等で各サーバーへコピーしなくてはなりません。

    サブドメインではっきり分けれれば良いのですが、ドメイン追加をできない場合は1443などの独自のポート番号でポートフォワードで振り分けて管理画面を動かすサーバーを固定したいところ。

    ただし、普通にポート番号付きでアクセスしてしまうと、管理画面でログインした直後にリダイレクトエラーが出ます。
    これは/data/config/config.php で設定している HTTP_URL や HTTPS_URL と異なるURLへジャンプすることを防ぐセキュリティ対策の影響です。
    では、そもそもHTTP_URL に:1443付きで定義したらどうかとなりますが、そうすると今後はポート番号無しでアクセスしているカート画面などでリダイレクトエラーが出ます。訪問者画面も管理画面も同じEC-CUBEだと設定も共通なので。

    そこで、ポート番号付きでアクセスしたときだけこのURLを書き換えてしまうと良いです。
    具体的には /data/config/config.php をこんな風に書きます。常時SSLのご時勢ですからHTTP_URLは振り分けなくても良いかな?

    define('HTTP_URL', 'http://www.exsample.co.jp/');
    if ($_SERVER['SERVER_PORT'] == '1443') {
      define('HTTPS_URL', 'https://www.exsample.co.jp:1443/');
    } else {
      define('HTTPS_URL', 'https://www.exsample.co.jp/');
    }

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE 2系で注文を受けた商品の規格を後で削除すると、管理画面で受注内容を編集できなくなる 2020.01.02
    A:

    注文された規格を削除して、dtb_products_classテーブルから該当する規格データが無くなると、受注内容のたとえば発送先住所などを編集しようとしても数量の上限チェックに引っかかりエラーが出て受注内容の変更ができなくなってしまいます。

    ※ この現象は、規格のチェックBOXを外して更新 → チェックBOXを再度立てて更新 でも発生します。見た目は同じ規格が存在しているように見えますが、内部のproduct_class_idが変わってしまうのが原因です

    product_class_idも引用してこれていないので画面処理もうまくいきません。
    対応策として、もし規格データが削除されていた場合は在庫数チェックを行わないようにする事で受注内容の編集をできるようにしてみます。

    まずdtb_products_classから規格データが無くなっていてもproduct_class_idやproduct_idを引用できるように商品詳細dtb_order_detailの方から参照するようにします
    SC_Helper_Purchase.php の
    function getOrderDetail のSQL文を書き換えます

    -            T3.product_id,
    -            T3.product_class_id as product_class_id,
    +            T2.product_id,
    +            T2.product_class_id as product_class_id,

    LC_Page_Admin_Order_Edit.php の
    function lfCheckError に処理をスルーさせるIF文を追加

                // 在庫数のチェック
                $arrProduct = $objProduct->getDetailAndProductsClass($arrValues['product_class_id'][$i]);
    
    +            // 規格が削除されていたら在庫数チェックしない
    +            if ($arrProduct['product_class_id'] == '') {
    +                continue;
    +            }

    商品種類 product_type_id も引用してこれていなくて、画面にはエラー表示されませんが必須チェックに引っかかり先に進めなくなるのでこれもスルーさせます。
    LC_Page_Admin_Order_Edit.php の
    function lfInitParam を修正

            // 受注詳細情報
    -        $objFormParam->addParam('商品種別ID', 'product_type_id', INT_LEN, 'n', array('EXIST_CHECK', 'MAX_LENGTH_CHECK', 'NUM_CHECK'), '0');
    +        $objFormParam->addParam('商品種別ID', 'product_type_id', INT_LEN, 'n', array('MAX_LENGTH_CHECK', 'NUM_CHECK'), '0');

    視覚的にも、規格が削除されていて在庫数が連動しない商品だと分かったほうが良いので、数量欄のあるTDの背景色を赤色で表示してみます。
    /data/Smarty/templates/admin/order/edit.tpl を修正

    -                    <td class="center">
    -                        <!--{assign var=key value="quantity"}-->
    +                    <td class="center"<!--{if $arrForm.product_type_id.value[$product_index] == ''}--> style="background-color:#fdd"<!--{/if}-->>
    +                        <!--{assign var=key value="quantity"}-->

    そもそも関連データが存在している規格データを容易にdeleteしてしまう実装ってどうなの?っていう話なんですが。

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE 2系でShift_JISに存在しない文字が含まれた受注データがあると、その受注内容が受注CSVからまるごと欠落する 2020.01.02
    A:

    受注データの備考などにお客様が入力した文章にShift_JISで表現できない文字や記号がまざっていると、受注CSVをダウンロードしたときにその受注データがそっくり欠落してしまいます。

    これはCSVデータをShift_JISに変換するときのiconvのパラメータがデフォルトの「不正な文字があったらデータを捨てる」指定になっているからです。
    受注データが抜け落ちてしまっては、発見が遅れて実務上のトラブルになりかねないので、変換できない文字があっても近似文字に置き換えて出力するTRANSLIT指定をつけた方が良いと思います。

    SC_Helper_CSV.php の
    function fopen_for_output_csv のiconv指定を変更

    -        stream_filter_append($fp, 'convert.iconv.utf-8/cp932');
    +        stream_filter_append($fp, 'convert.iconv.utf-8/cp932//TRANSLIT');

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE 2系で商品情報をCSVで更新するとき関連商品情報が削除される 2020.01.02
    A:

    CSVアップロードして商品データを更新する事ができるのですが、そのCSVに「関連商品」の列が無いと、CSVをアップロードしたときにその商品に今まで登録されていた関連商品の内容が全て削除されてしまいます。

    これは、CSVアップロード処理が「関連商品」に関する列がCSVに存在している事を前提に処理しているからで、関連商品がCSV列名に存在しているかどうかチェックして、列が無かったら関連商品の処理を行わないようにすることで回避できます。

    LC_Page_Admin_Products_UploadCSV.php の
    function lfRegistReccomendProducts の冒頭に以下のコードを挿入

            $cnt = 0;
            for ($i = 1; $i <= RECOMMEND_PRODUCT_MAX; $i++) {
                $keyname = 'recommend_product_id' . $i;
                $comment_key = 'recommend_comment' . $i;
                if (array_key_exists($keyname, $arrList) == true) $cnt++;
                if (array_key_exists($comment_key, $arrList) == true) $cnt++;
            }
            if ($cnt == 0) return;

    ※ 自分でコミュニティへ参加してパッチを作成するマンパワーが無いのでその予定はありません

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE 2系で商品を沢山購入すると住所情報が欠落する 2020.01.02
    A:

    以外と有名な問題なのですが、カートに沢山の商品を入れて注文すると、受注メールは送信されてエラーも出ないのに「送料がゼロ円になる」とか「管理画面に受注データが表示されない」という障害が起きます。

    これは、セッション情報を保存するDBテーブルが text型で取られているため、65,535バイト(MySQLの場合)以上のセッション情報を保存しようとすると後ろの方のデータが欠落するためです。

    CREATE TABLE dtb_session (
        sess_id text NOT NULL,
        sess_data text,
        create_date timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
        update_date timestamp NOT NULL,
        PRIMARY KEY (sess_id(255))
    );

    このセッションデータには、カートに入れた商品の情報+購入者情報+配送先情報が入るため、欠落するときは真っ先に配送先の情報が欠落してしまい、結果として都道府県が分からなくなるので送料が計算できずにゼロ円になってしまいます。また、受注データとJOINする配送先データが生成されないので一覧に受注データが表示されないという症状に繋がります。

    対策としては、text → logtext に型を変更するだけで65Kバイト→4Gバイトまで保持できるようになるので情報欠落は無くなります。sessionデータはdtb_order_tempテーブルにも持っているのでそちらも修正が必要です。

    alter table dtb_session modify sess_data longtext;
    alter table dtb_order_temp modify session longtext;

    ※ かなり以前から報告されている障害ですがバージョン2.13.5でも対策されていないので公式に対応する予定は無いようです
    ※ 自分でコミュニティへ参加してパッチを作成するマンパワーが無いのでその予定はありません

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE 2系の「もっと見る」を簡潔に 2020.01.02
    A:

    今更ながら、EC-CUBE 2系のスマホ版のデザイン変更で苦労しました。

    EC-CUBEに組み込まれている「もっと見る」ボタンですが、Ajaxで次ページのjsonデータを取得してきて画像、商品名、価格等のspanやimgタグの中身を1個ずつコピーしている。。。(3系でも同じなのかな??)

    これでは、つぶしが気かないったらありゃしないので、デザイン変更しても動作するJavaScriptに書き換えてみました。jsonではなく2ページ目のHTMLをそのままGETしてきて商品一覧部分のタグをそのまま現在の一覧の下に差し込むだけ。

    ・総HIT数が<span id=”productscount”>XXX</span>に書かれていること。
    ・もっと見るボタンにid=”btn_more_product”が付いていること。
    ・商品リストの個別商品が class=”list_area” に入っていて、各商品ブロックが<form>で囲まれていること。
    (<form>で囲まれていないなら、2ヶ所の .closest(‘form’) は不要)

    の3点だけ守られていれば、デザイン変更しても大丈夫です。

    <script>
        var pageNo = 2;
        var url = "<!--{$smarty.const.P_DETAIL_URLPATH}-->";
        var imagePath = "<!--{$smarty.const.IMAGE_SAVE_URLPATH|sfTrimURL}-->/";
        var statusImagePath = "<!--{$TPL_URLPATH}-->";
    	
        function getProducts(limit) {
            $.mobile.showPageLoadingMsg();
            var i = limit;
            //送信データを準備
            var postData = {};
            $('#form1').find(':input').each(function(){
                postData[$(this).attr('name')] = $(this).val();
            });
            postData["mode"] = "html";
            postData["pageno"] = pageNo;
    
            // デザイン変更に左右されない「もっと見る」処理に書き換えました。
            $.ajax({
                type: "POST",
                data: postData,
                url: "<!--{$smarty.const.ROOT_URLPATH}-->products/list.php",
                cache: false,
                dataType: "html",
                error: function(XMLHttpRequest, textStatus, errorThrown){
                    alert(textStatus);
                    $.mobile.hidePageLoadingMsg();
                },
                success: function(result){
                    $('.list_area:last').closest('form').after($(result).find('.list_area').closest('form').clone(true));
                    pageNo++;
    
                    //すべての商品を表示したか判定
                    if (parseInt($("#productscount").text()) <= $(".list_area").length) {
                        $("#btn_more_product").hide();
                    }
                    $.mobile.hidePageLoadingMsg();
                }
            });
        }
    </script>
    

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBEをインストールする 2019.12.10
    A:

    EC-CUBEを利用することになりそうなので、ひとまずEC-CUBEをレンタルサーバーにインストールしてみました。その手順備忘録です。

    EC-CUBEをダウンロードする

    EC-CUBE公式サイトからファイルをダウンロードします。
    2019年12月時点で最新バージョンは、2.13.5です。なのでダウンロードしたファイルは eccube-2.13.5.zipでした。
    screencapture-ec-cube-net.jpg
    ダウンロードするには、EC-CUBEメンバーである必要があります。登録は無料なので迷わず登録しました。

    補足ですが、バージョンによっては不具合修正ファイルがあることもあるようなので、その場合はその不具合修正ファイルもダウンロードしておきます。(今回はありませんでした。)

    EC-CUBEのファイルを配置する

    ダウンロードしたファイルは圧縮ファイルなのでとりあえず解凍します。
    そして、サーバーに配置する前に少し準備します。

    解凍すると以下のフォルダが存在すると思います。
    /data
    /docs
    /html
    /test
    /tests
    必要になのは「data」「html」の2つだけです。
    (不具合修正ファイルがある場合は上書きしてください。)

    これから構築するサイトのURLを「http://ドメイン/html」ではなく「http://ドメイン/」としたいので、「data」フォルダを丸ごと「html」フォルダ直下に移動します。
    その移動に伴い、html/define.phpを修正します。

    (修正前)

    1
    define("HTML2DATA_DIR", "/../data/");

    (修正後)

    1
    define("HTML2DATA_DIR", "/data/");

    次に「html」フォルダ直下にある.htaccessを削除します。

    ↓↓↓↓↓ 重要 ↓↓↓↓↓
    このままでは、dataフォルダにWEbからアクセスできてしまうため、とっても危険です。
    例えばlogファイルが丸見えになってしまうので、管理画面のログインIDやサーバーのパスや何のプラグイン使ってるかーなど、いろいろとデータが取れてしまうのです。(他にもいろいろ問題アリ。)
    そこで、dataフォルダ直下に以下を記述した.htaccessを配置します。

    1
    2
    order allow, deny
    deny from all

    ほんとは、dataフォルダはhtmlフォルダ内じゃなくて、ルートディレクトリ外に配置できる方が理想です。
    今回はどうしてもそれが出来ない環境の場合を例にしています。
    ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

    ここまで出来たら、サーバーに配置します。
    配置する場所はサーバーによって異なりますが、たいていはApacheのドキュメントルート直下に「html」フォルダ内のフォルダ、ファイル全てを配置します。

    データベースを作る

    EC-CUBEはデータベースを必要とするのでデータベースを事前に準備します。
    PostgreSQLかMySQLを使用することができます。私はphpMyAdminを使ってMySQLを作成しました。
    後で必要になるので、データベース名、MySQLユーザー名、MySQLパスワード、ホスト名をメモしておきます。

    EC-CUBEをインストールする

    「http://ドメイン/」にアクセスします。以下の画面が表示されたら「次へ進む」ボタンを押します。
    eccube-ins-02

    チェック結果が表示されます。成功していれば「次に進む」ボタンを押します。
    eccube-ins-03

    必要なファイルのコピーが開始されます。これも成功していれば「次に進む」ボタンを押します。
    eccube-ins-04

    ECサイトの設定をします。
    純国産のCMSというだけあって説明が丁寧です。さすがです。必要事項を入力して次へ進みましょう。
    eccube-ins-05

    データベースの設定です。
    事前に準備したデータベースの情報を入力します。DBの種類を間違えないようにしましょう。
    入力できたら「次へ進む」ボタンを押します。
    eccube-ins-07

    データベースの初期化です。
    eccube-ins-08
    eccube-ins-09

    デバッグのために情報提供を依頼されます。無難に「はい(推奨)」にしました。「いいえ」にしたらどうなるかは、試してないのでわかりません。
    eccube-ins-10

    インストール完了です。管理画面ログインにすすみます。
    eccube-ins-11

    ここで画面上部に警告が出ます。install/index.phpを削除しろと言っています。おとなしく従いましょう。
    eccube-ins-12

    ファイルを削除するとログイン画面から警告が消えるので、改めて管理画面にログインします。
    eccube-ins-13

    これにて終了です。

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE2.13カスタマイズ:新しいページを追加する 2019.11.11
    A:

    EC-CUBEで新しいページを追加する方法として、管理画面で追加して、そのページのURLを任意に変更する手順をメモ。

    管理画面からページを追加する

    管理画面でデザイン管理>PC>ページ詳細設定を開き、必要事項を入力して登録するボタンを押下。
    ※スマホ、モバイルもそれぞれ同様です。
    EC-CUBE-add-page-01

    これにより以下の追加が行われます。
    1)/html/user_data/sample.phpが生成される。
    ※sample.phpは登録した任意のファイル名
    ※処理はこのファイルに記述していきます
    2)/data/Smarty/templates/(テンプレート名)/user_data/sample.tplが生成される。
    ※管理画面から入力した内容が入ります。
    3)dtb_pagelayoutテーブルにこのページ情報が1レコード追加される。

    作成されたページのこの時点では以下のパスで確認できます。
    http://www.example.com/html/user_data/sample.php
    このままではイケテないので以下になるように変更していきたいと思います。
    http://www.example.com/html/sample/

    追加したページのURLを変更する

    phpファイルを移動する

    まず生成されたphpファイルを以下に移動します。フォルダを新規作成してファイル名も変更します。
    /html/sample/index.php

    DBを書き換える

    dtb_pagelayoutテーブルのurlフィールドをsample/index.phpに変更する。
    これで、URLの変更ができました。

    ちなみに、dtb_pagelayoutテーブルの中身ですが、おそらくこんな感じ。
    device_type_id・・・「10」はPC用のページという意味
    page_id・・・同じdevice_type_idで重複しないID
    page_name・・・ページ名(管理画面で表示している名前)
    url・・・ページのURL
    filename・・・tplファイルの保管場所
    edit_flg・・・削除可能フラグ

    注:インストールされた環境によってパスは異なります。一般的なパスで紹介しています。

    2014.09.08 追記
    追加したページのURLを変更した場合は、ページを更新するときに管理画面のデザイン管理>PC>ページ詳細設定から編集できなくなります。
    編集してしまうとdtb_pagelayoutテーブルのurlフィールドがデフォルトに戻ってしまうからです。
    更新はテンプレートを直接しなくてはなりません。
    んー、イマイチ?URLを変更しても管理画面から更新できる方法ないかなー?

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] イベントセット販売プラグインマニュアル 2019.11.07
    A:
    • プラグイン登録
      1. ファイルの選択ボタンをクリックします。
      2. 登録するファイル(EventSetSale.tar.gz)を選択し、開くをクリックします。
      3. インストールのボタンをクリックして、待って、インストールを待ちます。
      4. 下の1-2画面の①の赤長方形のボックスのように表示されたら、通常のインストールがされました。
        1-2.インストール完了画面:通常のインストールが完了した画面
      5. プラグインの基本的な設定をするために②プラグインの設定をクリックして設定画面を開いて、各値を入力して設定します。
      6. インストールされてプラグインを動作させるために1-2画面の③有効チェックボックスをクリックしてチェック 表示をします。

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] 2.13系 ソフトウェア要件 2019.10.11
    A:
    分類 ソフトウェア 動作確認済み
    WEBサーバ IIS 7.0~
    Apache 2.0.x~ 2.2.x~
    言語 PHP 5.2~
    データベース PostgreSQL 8.1.4~ 9.x~
    MySQL 5.0.x~

    必須PHPライブラリ

    • pgsql / mysql
    • gd
    • freetype2
    • mbstring
    • zlib
    • ctype
    • spl (PHP 5.3.0 未満の場合)
    • session

    推奨PHPライブラリ

    • JSON(PHP5.2以降でオーナーズストアを使用する場合は必須)
    • xml(プラグイン機能を使用する場合は必須)
    • OpenSSL
    • cURL
    • hash
    • mhash (PHP 5.3.0 未満の場合)
    • mcrypt
    • zip

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] ECCUBE 2.13のインストール方法をスクリーンショット付きでわかりやすく徹底解説! 2019.10.10
    A:

    EC-CUBE v2.13 インストール에 대한 이미지 검색결과


    EC-CUBE」とは、ECサイトに必要なカート機能や決済機能がパッケージ化されている、無料のツールです。ECサイトを運営したことがある人なら、きっと知っている人も多いはず。あとはWebサーバーさえあれば、わりと簡単にECサイトを立ち上げることができます。これからECサイトをはじめる方向けに、EC-CUBE バージョン2.13のインストール方法を、各画面のスクリーンショット付きでわかりやすく解説します。


    まずはダウンロード

    EC-CUBEサイトから、ZIPファイルをダウンロードしましょう。(この記事では、バージョンは2.13.2です。)

    なお、ダウンロードには会員登録が必要です。

    ECCUBEダウンロード

    つづいてメンバー登録

    ECCUBE 新規メンバー登録

    「eccube-2.13.2.zip」がダウンロードできたら、解凍してみましょう。
    すると、下記のようにいろいろなファイルが入っているはずです。
    このうち「data」と「html」の2つだけを、Webサーバーにアップロードしましょう。

    ECCUBE解凍フォルダ

    アップロード

    アップロードする前に、FTPソフトの設定でアップロード時のパーミッションを指定しておくことをおすすめします。
    ファイルは「644」、ディレクトリは「755」で設定しておきましょう。

    なお、data はセキュリティの面で、一般のユーザーから見えない階層にアップしたほうが良いです。

    私の場合はこんな感じ↓

    /root/data(=一般のユーザーは見れない領域)
    /root/www/html(=www配下は一般のユーザでも見れる領域)

    define.phpを編集

    私の場合は data と html が同階層でなくなったので、html 内の define.php を変更する必要があります。
    (data と html が同階層であれば、変更する必要はありませんので、このステップは飛ばして構いません)

    html ディレクトリから見た、dataディレクトリの相対パスを指定しましょう。

    [php]
    /** HTMLディレクトリからのDATAディレクトリの相対パス */
    define(‘HTML2DATA_DIR’, ‘../../data/’);
    [/php]


    データーベースを作成

    ECCUBEのインストール時にはデータベースの情報を求められますので、あらかじめ用意しておきましょう。

    ここでは私が利用しているさくらサーバーでのやり方をご紹介します。

    コントロールパネルにログイン。

    左ナビから「データベースの設定」を選択。

    データーベースの設定を選択

    「データベースの新規作成」を選択。

    データベースの新規作成を選択

     

    データーベースの情報を設定します。
    MySQLのバージョンは「5.5」、
    データベース名は、自由に命名してください。
    文字コードは「UTF-8」にします。

     

    データーベースの情報を入力

    これでデータベースが作成されました!


    ECCUBEのインストール画面

    先ほどアップした「html」ディレクトリにアクセスしてみましょう。

    パーミッションとdefine.phpのパスが正しく設定されていれば、次の画面が現れるはずです。

    ECCUBE インストール

    ECCUBE - インストール02

    ひとまず必須項目を入力していきます。
    これらの項目はインストール後でも管理画面から変更できます。

    ECCUBE - インストール04

    先ほど作成したデータベースの情報を入力していきます。
    さくらのコントロールパネルのデータベース一覧画面を参照してコピペしましょう。

    ECCUBE - インストール05

    つづいて、データベースの初期化を行います。(ちょっとドキドキ)

    ECCUBE - インストール06

    データベースの初期化に成功しました。

    ECCUBE - インストール07

    情報提供を送信するかをたずねられますが、これはどちらでも構いません。

    ECCUBE - インストール08

    以上でインストールが完了しました!

    ECCUBE - インストール09

    htmlディレクトリにアクセスすると、デフォルトのストアページが表示されるはずです。

    ECCUBE - 初期サイト

    次回は、管理画面から商品情報を変更してみます。

    Facebook [ko]댓글 

  • Q: [EC-CUBE 2系] 単体テストガイドライン 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 [ko]댓글 

  • Q: [EC-CUBE 2系] リファクタリングガイドライン 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 [ko]댓글 

  • Q: [EC-CUBE 2系] EC-CUBE標準規約 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 [ko]댓글 

Board Pagination Prev 1 Next
/ 1