○:標準搭載機能。要件によって個別のカスタマイズが必要な可能性があります。
PLG:プラグインにて対応可能です。
|
原文出所 | https://www.ec-cube.net/product/functions.php |
---|
○:標準搭載機能。要件によって個別のカスタマイズが必要な可能性があります。
PLG:プラグインにて対応可能です。
|
○:標準搭載機能。要件によって個別のカスタマイズが必要な可能性があります。
PLG:プラグインにて対応可能です。
|
○:標準搭載機能。要件によって個別のカスタマイズが必要な可能性があります。
PLG:プラグインにて対応可能です。
|
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/'); }
注文された規格を削除して、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してしまう実装ってどうなの?っていう話なんですが。
受注データの備考などにお客様が入力した文章に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');
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;
※ 自分でコミュニティへ参加してパッチを作成するマンパワーが無いのでその予定はありません
以外と有名な問題なのですが、カートに沢山の商品を入れて注文すると、受注メールは送信されてエラーも出ないのに「送料がゼロ円になる」とか「管理画面に受注データが表示されない」という障害が起きます。
これは、セッション情報を保存する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でも対策されていないので公式に対応する予定は無いようです
※ 自分でコミュニティへ参加してパッチを作成するマンパワーが無いのでその予定はありません
今更ながら、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>
EC-CUBEを利用することになりそうなので、ひとまずEC-CUBEをレンタルサーバーにインストールしてみました。その手順備忘録です。
EC-CUBE公式サイトからファイルをダウンロードします。
2019年12月時点で最新バージョンは、2.13.5です。なのでダウンロードしたファイルは eccube-2.13.5.zipでした。
ダウンロードするには、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パスワード、ホスト名をメモしておきます。
「http://ドメイン/」にアクセスします。以下の画面が表示されたら「次へ進む」ボタンを押します。
チェック結果が表示されます。成功していれば「次に進む」ボタンを押します。
必要なファイルのコピーが開始されます。これも成功していれば「次に進む」ボタンを押します。
ECサイトの設定をします。
純国産のCMSというだけあって説明が丁寧です。さすがです。必要事項を入力して次へ進みましょう。
データベースの設定です。
事前に準備したデータベースの情報を入力します。DBの種類を間違えないようにしましょう。
入力できたら「次へ進む」ボタンを押します。
データベースの初期化です。
デバッグのために情報提供を依頼されます。無難に「はい(推奨)」にしました。「いいえ」にしたらどうなるかは、試してないのでわかりません。
インストール完了です。管理画面ログインにすすみます。
ここで画面上部に警告が出ます。install/index.php
を削除しろと言っています。おとなしく従いましょう。
ファイルを削除するとログイン画面から警告が消えるので、改めて管理画面にログインします。
これにて終了です。
EC-CUBEで新しいページを追加する方法として、管理画面で追加して、そのページのURLを任意に変更する手順をメモ。
管理画面でデザイン管理>PC>ページ詳細設定
を開き、必要事項を入力して登録するボタンを押下。
※スマホ、モバイルもそれぞれ同様です。
これにより以下の追加が行われます。
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/
まず生成されたphpファイルを以下に移動します。フォルダを新規作成してファイル名も変更します。/html/sample/index.php
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を変更しても管理画面から更新できる方法ないかなー?
EC-CUBE3에서는 관리 화면의 사이드 메뉴에 쉽게 메뉴를 추가 할 수 있습니다.
외형 수정이므로 템플릿 (twig)를 편집 ...라고 생각했는데, 수정할 필요가있는 것은 ServiceProvider입니다.
실제로 ServiceProvider에서하지 않아도 좋다고 생각 합니다만,로드 된 적당한 ServiceProvider 다음의 설명합니다.
$ app [ 'config'] = $ app-> share ($ app-> extend ( 'config', function ($ config) { $ nav = array ( 'id'=> 'admin_new_menu' 'name'=> '새 메뉴' 'url'=> 'admin_new_menu' 'has_child'=> 'false' 'icon'=> 'cb-chart' 'child'=> array ( array ( 'id'=> 'xx_submenu1' 'url'=> 'xx_submenu1' 'name'=> '하위 메뉴 1' ), ), ); $ config [ 'nav'] [] = $ nav; return $ config; }));
이렇게 app.config.nav 메뉴 배열을 추가하여 메뉴를 늘릴 수 있습니다.
반대로 필요없는 항목이 배열로부터 삭제 해 주면 제거 할 수 있습니다.
EC-CUBE3를 이용하시는 분은 꼭보세요.
管理者としてログインして、管理者メニューから「オーナーズストア」メニューのサブメニューの上部にある「プラグイン管理」メニューをクリックして、プラグインの登録を行います。
プラグイン管理でインストールが完了したら、プラグインの基本設定をするために②「プラグイン設定」をクリックして設定画面を開きます。
イベントセット販売機能の詳細な設定をします。
イベントセット販売機能では、各イベントセットの登録・編集・削除を管理します。
イベントセットを登録または管理のために「商品管理」メニューから「イベントセット機能管理」メニューを選択します。
カスタマイズ
編を活用してください。イベントセット適用は、블록으로 구성되어 각 페이지의 원하는 장소에 삽입 가능하며 별도의 페이지를 만들기도 가능합니다。
今回は商品管理の一番下に新規メニュー項目を追加することを考えます。
まずメニューから開けるページが必要ですのでコントローラーを作成し、新しいページを作った後でそのページをメニューに追加してみましょう。今回はeccube_nav.yamlを編集するもっとも簡単な方法をご紹介します。
管理画面に新規ページを作る方法は下記URLで紹介しています。
管理画面に2ステップで新規ページを作る方法
それでは、このページを商品管理のメニューに追加してみます。
初期状態での管理画面のメニュー一覧はapp/config/eccube/packages/eccube_nav.yamlに配列として記載されています。
商品のメニュー部分は下記のようになっています。
parameters: eccube_nav: product: name: admin.product.product_management icon: fa-cube children: product_master: name: admin.product.product_list url: admin_product product_edit: name: admin.product.product_registration url: admin_product_product_new class_name: name: admin.product.class_management url: admin_product_class_name class_category: name: admin.product.category_management url: admin_product_category product_tag: name: admin.product.tag_management url: admin_product_tag product_csv_import: name: admin.product.product_csv_upload url: admin_product_csv_import category_csv_import: name: admin.product.category_csv_upload url: admin_product_category_csv_import #商品管理に新規メニュー追加
category_csv_importの下に次のように追記することでメニューを追加することができます。
product_new_menu: name: 新メニュー url: admin_new_menu
new_menuはメニューにつけるID名で、このメニューをアクティブにしたい時にtwigから指定します。
nameは表示する名称
urlはコントローラーのRouteで指定したURLの名前です。
これで管理画面にアクセスするとカテゴリCSV登録の下に新規メニューが追加されました。twigでメニューの位置を指定しているのでアクティブ表示になっています。
eccube_nav.yamlを編集するとバージョンアップの際に上書きされてしまう可能性があります。
大きなカスタマイズをするとバージョンアップできない可能性も高いですが、バージョンアップをご検討の方はEccubeNav(NavCompilerPass)の仕組みを利用するか、app/config/eccube/packages/prodなどに複製して用いると良いかもしれません。
分類 | ソフトウェア | 動作確認済み |
---|---|---|
WEBサーバ | IIS | 7.0~ |
Apache | 2.0.x~ 2.2.x~ | |
言語 | PHP | 5.2~ |
データベース | PostgreSQL | 8.1.4~ 9.x~ |
MySQL | 5.0.x~ |
「EC-CUBE」とは、ECサイトに必要なカート機能や決済機能がパッケージ化されている、無料のツールです。ECサイトを運営したことがある人なら、きっと知っている人も多いはず。あとはWebサーバーさえあれば、わりと簡単にECサイトを立ち上げることができます。これからECサイトをはじめる方向けに、EC-CUBE バージョン2.13のインストール方法を、各画面のスクリーンショット付きでわかりやすく解説します。
EC-CUBEサイトから、ZIPファイルをダウンロードしましょう。(この記事では、バージョンは2.13.2です。)
なお、ダウンロードには会員登録が必要です。
つづいてメンバー登録
「eccube-2.13.2.zip」がダウンロードできたら、解凍してみましょう。
すると、下記のようにいろいろなファイルが入っているはずです。
このうち「data」と「html」の2つだけを、Webサーバーにアップロードしましょう。
アップロードする前に、FTPソフトの設定でアップロード時のパーミッションを指定しておくことをおすすめします。
ファイルは「644」、ディレクトリは「755」で設定しておきましょう。
なお、data はセキュリティの面で、一般のユーザーから見えない階層にアップしたほうが良いです。
私の場合はこんな感じ↓
/root/data(=一般のユーザーは見れない領域)
/root/www/html(=www配下は一般のユーザでも見れる領域)
私の場合は data と html が同階層でなくなったので、html 内の define.php を変更する必要があります。
(data と html が同階層であれば、変更する必要はありませんので、このステップは飛ばして構いません)
html ディレクトリから見た、dataディレクトリの相対パスを指定しましょう。
[php]
/** HTMLディレクトリからのDATAディレクトリの相対パス */
define(‘HTML2DATA_DIR’, ‘../../data/’);
[/php]
ECCUBEのインストール時にはデータベースの情報を求められますので、あらかじめ用意しておきましょう。
ここでは私が利用しているさくらサーバーでのやり方をご紹介します。
コントロールパネルにログイン。
左ナビから「データベースの設定」を選択。
「データベースの新規作成」を選択。
データーベースの情報を設定します。
MySQLのバージョンは「5.5」、
データベース名は、自由に命名してください。
文字コードは「UTF-8」にします。
これでデータベースが作成されました!
先ほどアップした「html」ディレクトリにアクセスしてみましょう。
パーミッションとdefine.phpのパスが正しく設定されていれば、次の画面が現れるはずです。
ひとまず必須項目を入力していきます。
これらの項目はインストール後でも管理画面から変更できます。
先ほど作成したデータベースの情報を入力していきます。
さくらのコントロールパネルのデータベース一覧画面を参照してコピペしましょう。
つづいて、データベースの初期化を行います。(ちょっとドキドキ)
データベースの初期化に成功しました。
情報提供を送信するかをたずねられますが、これはどちらでも構いません。
以上でインストールが完了しました!
htmlディレクトリにアクセスすると、デフォルトのストアページが表示されるはずです。
次回は、管理画面から商品情報を変更してみます。
開店準備 | 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 |
本ガイドラインはEC-CUBEの単体テストをPHPUnitを使って行う上でのガイドラインを
株式会社SHIFT様(http://www.shiftinc.jp/)のご協力によりまとめたものとなります。
テストコードを含んだフォルダ構成は以下のようになります。
tests以下には、テストコード本体の他にテスト用のユーティリティや設定ファイル等が含まれます。
build.xml | テストやインスペクションを行うための設定ファイルです。 |
tests | |
├phpunit.xml | PHPUnitで使う各種設定を記載したファイルです。 SVN上にはphpunit.xml.baseというファイルがありますが、ローカルではこれをコピーしてphpunit.xmlを作成してください。 |
├ruleset.xml | PHP_CodeSniffer(インスペクションツール)用の設定ファイルです。 |
├require.php | テストに必要なファイルをインクルードするためのクラスです。 SVN上にはrequire.php.baseというファイルがありますが、ローカルではこれをコピーしてrequire.phpを作成してください。 |
└class | テスト用のクラスを格納するディレクトリです。 |
├Common_TestCase.php | 他のテストクラスの基底となるクラスです。 |
├replace | テスト用に実装を入れ替えているクラスを格納するディレクトリです。 |
└test/util | テスト用のユーティリティを格納するディレクトリです。 |
テストコードはそれぞれ対応するソースコードと同じ階層に保存します。
単体テストを実行するためには、ローカルの環境にPHPUnitをインストールしておく必要があります。
また、インクルードパス等をローカルの環境に合わせて書き換えるため、SVNに含まれているファイルをコピーしてローカル用の設定ファイルを作成する必要があります。
手順は下記の通りです。
tests/phpunit.xml、tests/require.phpはsvn:ignoreに設定されているため、自由に書き換えてもコミットはされません。
全体のテストを行う場合には、phingのtestターゲットを実行します。
テストの内容はbuild.xmlの中に定義されているため、実際にはphpunitコマンドが発行されます。
% phing test
テストが完了すると、結果がreportsディレクトリ以下に出力されます。
個々のテストを行う場合には、テスト対象のディレクトリを指定して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
テストクラスは、基本的に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(); } }
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(); }
原則として、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条件の前提を守った上で、なるべく条件分岐を網羅するようにテストを作成していきます。
テストfunctionの名称は、テストの内容を分かりやすくするため
test【function名】_【条件】_【期待する結果】()
という形式で統一します。命名規則を統一することで可読性が上がり、
テストコードを書いた本人でなくてもJenkinsのテストレポートを見るとどのようなテストが行われているかが一目でわかります。
また、条件・期待する結果は日本語で記載することでさらに分かりやすくすることができます。
testAction_必須項目が入力されていない場合_エラー画面に遷移する()
テストクラスはテスト対象のfunction毎に1つずつ分けて作成します。
ただし、後述する「定数による条件分岐」をテストする場合には条件毎にクラスを分ける必要があるためさらに細分化されます。
上で述べたとおり基本的にテストクラスはテスト対象のfunctionに対応するため、
【対象クラス】_【対象function】Test.php
という名称にします。さらに条件毎にファイルを分ける場合には、
【対象クラス】_【対象function】_【条件】Test.php
とします。
LC_Page_Products_Detail_ActionTest.php LC_Page_Products_Detail_Action_HasErrorTest.php
条件によってファイル名を分ける場合には、ファイル名は日本語を避けて定義するようにしてください。
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より先に指定したい定数を定義することで、このテストクラスに限定した値を定義することができます。
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したかどうか'); }
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()の返り値がランダムなのでテストで要求する値を返却させるためにオーバーライドしています。
テストで常に同じ結果を得られるようにするには、テスト実行のたびに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以下のユーティリティクラスから取得できるようにしておき、データの再利用性を高めます。
端末の種類が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('端末種別'); }
ユーザがログインしているかどうかによって処理が分岐する場合は、セッションの情報と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('ログイン状態'); }
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 関数は MVC のコントローラにあたり以下の処理を記述する.
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: } }
$_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'); }
<?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();
関数の PHPDoc コメントは必ず記述する.
error_reporting(E_ALL) で, エラー及び警告が出力されないようコーディングすること.
PHP5.3 で非推奨となっている関数は使用しないこと.
以下の機能も, PHP5.3.x で非推奨となっているが, PHP4互換のために使用しても良い EC-CUBE 2.12.0 から PHP4 は非対応となりました.
参考 http://www.php.net/manual/ja/migration53.deprecated.php
URL のファイルパス部の取得は $_SERVER['PHP_SELF'] を使わず、代わりに $_SERVER['SCRIPT_NAME'] を使用する。(参考: #1717)
基本的に Zend Framework PHP 標準コーディング規約 に順ずる.
以下, 要点及び相違点を規定する.
また, コーディングに際して, 以下のガイドラインに沿うことが望ましい
PHPUnitを利用した単体テストを行う際は以下のガイドラインに沿う事が望ましい
Prefix | 種類 | 例 |
---|---|---|
SC, GC | 1つのサイト内で共有するクラス (他のクラスから呼ばれる) | SC_Customer.php |
LC | 1つのソースファイル内で使用するクラス (通常他のクラスから呼ばれない) | LC_Page_Abouts.php |
Prefix | 種類 | 例 |
---|---|---|
sf | 一つのサイト内で共有する関数 | sfGetProductName() |
lf | 一つのソースファイル内で使用する関数 | lfGetProductName() |
fn | JavaScript で宣言された関数 | fnGetProductName() |
Prefix | 種類 | 例 |
---|---|---|
obj | クラス変数(オブジェクト) | $objQuery |
arr | 配列 | $arrCustmers |
tpl | テンプレート変数(リテラル) | $this->tpl_csv_id |
Prefix | 種類 | 例 |
---|---|---|
mtb | マスタデータ | mtb_pref |
dtb | データテーブル | dtb_order_detail |
<?php class LC_Page_Abouts { function process() { // some logic. } }
<?php if ($flag == '1') { // '1' == $flag と書くのは NG $ret = 1; } elseif ($flag == '2') { $ret = 2; } else { $ret = 3; }
<?php for ($i = 0; $i < $max; $i++) { echo $i . "\n"; }
<?php foreach ($var as $key => $val) { echo $key . ':' . $val . "\n"; }
<?php while ($flag) { var_dump($flag); }
<?php do { var_dump($flag); } while ($flag);
<?php switch ($var) { case VAR_ONE: echo 'one'; break; case VAR_TWO: echo 'two'; break; default: echo 'default'; break; }
<?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__;
<?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. */
<?php /** * クラスの簡単な説明 * * クラスの詳細な説明.... * ... * * @package Page * @author LOCKON CO.,LTD. * @version $Id$ */ class LC_Page { /** メンバ変数 */ var foo; }
<?php /** * 関数の簡単な説明. * * 関数の詳細な説明.... * .... * * @access private * @param string $foo 引数の説明 * @param string|integer $bar 引数の説明 * @return string 返り値の説明 */ function process($foo, $bar = '') { // some process... return 'string value'; }
<?php // TODO リファクタリングすること. // FIXME 要修正. バグの説明(#135) /** * :XXX: 以下, 怪しいロジック(#999) * 複数行のコメントはこのように. * * 必要に応じて チケットの ID も記述する. */
Facebook [ja]コメント