Monthly Archives: 12月 2018

PHPの array_column array_combine は超便利

PHPでデータベースを読み書きして操作していると、連想配列が配列になった以下のような配列からデータを取り出して操作する事が多いと思います。

$rec = array(
	array(
		'id' => '1001',
		'name' => 'りんご',
		'area' => '青森'
	),
	array(
		'id' => '1002',
		'name' => '梨',
		'area' => '鳥取'
	),
	array(
		'id' => '1003',
		'name' => 'ぶどう',
		'area' => '山梨'
	)
);

たとえば、areaの配列を取り出したいとき

foreachを使うと以下のようになりますが。

$ary = array();
foreach ($rec as $d) {
	$ary[] = $d['area'];
}

これを array_column を使うとこう書けます。

$ary = array_column($rec, 'area');

結果はこう

$ary = array(
	'青森',
	'鳥取',
	'山梨'
);

また、idをキーにしたnameの配列を取り出したいときは array_column の第2引数にキーにしたい項目のキー名を指定します

foreachだと以下のように書きますが

$ary = array();
foreach ($rec as $d) {
	$ary[$d['id']] = $d['name'];
}

これを array_column を使うとこう書けます。

$ary = array_column($rec, 'name', 'id');

結果はこう

$ary = array(
	'1001' => 'りんご',
	'1002' => '梨',
	'1003' => 'ぶどう'
);

また、idをキーにして$recの各項目のArrayをそのまま保持した配列を取り出したいときは、array_combine という第1引数の配列をキーにして、第2引数の配列を値として1つの配列を組み立てる関数を array_column と組み合わせて使うと簡単です。

foreachだと以下のように書きますが

$ary = array();
foreach ($rec as $d) {
	$ary[$d['id']] = $d;
}

これを array_column と array_combine を使ってこう書けます。

$ary = array_combine(array_column($rec, 'id'), $rec);

結果はこう

$ary = array(
	'1001' => array(
		'id' => '1001',
		'name' => 'りんご',
		'area' => '青森'
	),
	'1002' => array(
		'id' => '1002',
		'name' => '梨',
		'area' => '鳥取'
	),
	'1003' => array(
		'id' => '1003',
		'name' => 'ぶどう',
		'area' => '山梨'
	)
);

コーディングがシンプルに、かつ見通しが良くなるので積極的に使っていきたいお勧め手法です。

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

注文された規格を削除して、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してしまう実装ってどうなの?っていう話なんですが。。

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

受注データの備考などにお客様が入力した文章に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');

EC-CUBE 2系で商品情報をCSVで更新するとき関連商品情報が削除される

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;

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

EC-CUBE 2系で商品を沢山購入すると住所情報が欠落する

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

これは、セッション情報を保存する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でも対策されていないので公式に対応する予定は無いようです
※ 自分でコミュニティへ参加してパッチを作成するマンパワーが無いのでその予定はありません