カテゴリー
EC-CUBE

【EC-CUBE4】1円ズレる原因

カスタマイズにより、マイナス伝票を作成していたプロジェクトで発生

通常の消費税計算へマイナス値を渡すと、切り捨て処理が反転する

[temp id=3]

消費税取得処理へ負数を使用すると端数処理が反転する。

対象コードを一部抜粋

ファイルパス:src/Eccube/Service/PurchaseFlow/Processor/TaxProcessor.php

PHP
<?php
// 税込表示の場合は, priceが税込金額のため割り戻す.
if ($item->getTaxDisplayType()->getId() == TaxDisplayType::INCLUDED) {
    $tax = $this->taxRuleService->calcTaxIncluded(
        $item->getPrice(), $item->getTaxRate(), $item->getRoundingType()->getId(),
        $item->getTaxAdjust());
} else {
    $tax = $this->taxRuleService->calcTax(
        $item->getPrice(), $item->getTaxRate(), $item->getRoundingType()->getId(),
        $item->getTaxAdjust());
}

端数を切り捨てにしている場合は、第三引数をnullにして繰切り上げにすれば良い

PHP
<?php
// 税込表示の場合は, priceが税込金額のため割り戻す.
if ($item->getTaxDisplayType()->getId() == TaxDisplayType::INCLUDED) {
    $tax = $this->taxRuleService->calcTaxIncluded(
        $item->getPrice(), $item->getTaxRate(), null,
        $item->getTaxAdjust());
} else {
    $tax = $this->taxRuleService->calcTax(
        $item->getPrice(), $item->getTaxRate(), null,
        $item->getTaxAdjust());
}
カテゴリー
EC-CUBE

【EC-CUBE4】価格を少数第二位まで登録する

卸価格で少数第二位まで使用する要件が上がった。

[temp id=3]

実装できるか調査

dtb_product_classのprice02に小数点が登録できるのは把握していた。

通過設定を変更

プログラムを調査していると、envの設定を変更すれば登録できるようになる事がわかった。

デフォルト(未定義):ECCUBE_CURRENCY=JPY

ドルへ変更:ECCUBE_CURRENCY=USD

管理画面:商品登録で少数第二位まで登録可能になった。

price01, price02両方ともに影響する。

各所円マークがドルマークへ変更される

price02のみ使用したい

PriceTypeのSCALEで、小数点以下の桁数を制御しているのがわかった。

ProductClassTypeExtensionを作成

app/Customize/Form/Extension/Admin/ProductClassTypeExtension.php

PHP
<?php
/**
 * price02へWholesalePriceTypeを適用する
 */
namespace Customize\Form\Extension\Admin;
use Customize\Form\Type\WholesalePriceType;
use Eccube\Form\Type\Admin\ProductClassType;
use Symfony\Component\Form\AbstractTypeExtension;
use Doctrine\ORM\EntityManagerInterface;
use Eccube\Common\EccubeConfig;
use Eccube\Entity\ClassCategory;
use Eccube\Form\DataTransformer;
use Eccube\Form\Type\Master\DeliveryDurationType;
use Eccube\Form\Type\Master\SaleTypeType;
use Eccube\Form\Type\PriceType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class ProductClassTypeExtension extends AbstractTypeExtension
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;
    /**
     * @var EccubeConfig
     */
    protected $eccubeConfig;
    /**
     * ProductClassType constructor.
     *
     * @param EntityManagerInterface $entityManager
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        EccubeConfig $eccubeConfig
    ) {
        $this->entityManager = $entityManager;
        $this->eccubeConfig = $eccubeConfig;
    }
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('code', TextType::class, [
                'required' => false,
                'constraints' => [
                    new Assert\Length([
                        'max' => $this->eccubeConfig['eccube_stext_len'],
                    ]),
                ],
            ])
            ->add('stock', NumberType::class, [
                'required' => false,
                'constraints' => [
                    new Assert\Regex([
                        'pattern' => "/^\d+$/u",
                        'message' => 'form_error.numeric_only',
                    ]),
                ],
            ])
            ->add('stock_unlimited', CheckboxType::class, [
                'label' => 'admin.product.stock_unlimited__short',
                'value' => '1',
                'required' => false,
            ])
            ->add('sale_limit', NumberType::class, [
                'required' => false,
                'constraints' => [
                    new Assert\Length([
                        'max' => 10,
                    ]),
                    new Assert\GreaterThanOrEqual([
                        'value' => 1,
                    ]),
                    new Assert\Regex([
                        'pattern' => "/^\d+$/u",
                        'message' => 'form_error.numeric_only',
                    ]),
                ],
            ])
            ->add('price01', PriceType::class, [
                'required' => false,
            ])
            ->add('price02', WholesalePriceType::class, [
            ])
            ->add('tax_rate', TextType::class, [
                'required' => false,
                'constraints' => [
                    new Assert\Range(['min' => 0, 'max' => 100]),
                    new Assert\Regex([
                        'pattern' => "/^\d+(\.\d+)?$/",
                        'message' => 'form_error.float_only',
                    ]),
                ],
            ])
            ->add('delivery_fee', PriceType::class, [
                'required' => false,
            ])
            ->add('sale_type', SaleTypeType::class, [
                'multiple' => false,
                'expanded' => false,
                'constraints' => [
                    new Assert\NotBlank(),
                ],
            ])
            ->add('delivery_duration', DeliveryDurationType::class, [
                'required' => false,
                'placeholder' => 'common.select__unspecified',
            ])
            ->addEventListener(FormEvents::POST_SUBMIT, function ($event) {
                $form = $event->getForm();
                $data = $form->getData();
                if (empty($data['stock_unlimited']) && is_null($data['stock'])) {
                    $form['stock_unlimited']->addError(new FormError(trans('admin.product.product_class_set_stock_quantity')));
                }
            });
        $transformer = new DataTransformer\EntityToIdTransformer($this->entityManager, ClassCategory::class);
        $builder
            ->add($builder->create('ClassCategory1', HiddenType::class)
                ->addModelTransformer($transformer)
            )
            ->add($builder->create('ClassCategory2', HiddenType::class)
                ->addModelTransformer($transformer)
            );
    }
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => 'Eccube\Entity\ProductClass',
        ]);
    }
    /**
     * {@inheritdoc}
     */
    public static function getExtendedTypes(): iterable
    {
        return [ProductClassType::class];
    }
}

PriceTypeをコピーして、SCALEを定数で2に設定したWholesalePriceTypeを作成

ファイルパス:app/Customize/Form/Type/WholesalePriceType.php

PHP
<?php
/**
 * 卸価格:price02専用Formクラス
 * 小数点第2位まで、登録できるように調整
 * 通貨タイプ:デフォルトJPYの場合は、小数点の桁数は0に設定されている
 */
namespace Customize\Form\Type;
use Eccube\Common\EccubeConfig;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Range;
class WholesalePriceType extends AbstractType
{
    const SCALE = 2;
    /**
     * @var EccubeConfig
     */
    protected $eccubeConfig;
    /**
     * @var ContainerInterface
     */
    protected $container;
    /**
     * PriceType constructor.
     *
     * @param EccubeConfig $eccubeConfig
     */
    public function __construct(EccubeConfig $eccubeConfig, ContainerInterface $container)
    {
        $this->eccubeConfig = $eccubeConfig;
        $this->container = $container;
    }
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $currency = $this->container->getParameter('currency');
        $scale = self::SCALE;
        $max = $this->eccubeConfig['eccube_price_max'];
        $min = -$max;
        $constraints = function (Options $options) use ($max, $min) {
            $constraints = [];
            // requiredがtrueに指定されている場合, NotBlankを追加
            if (isset($options['required']) && true === $options['required']) {
                $constraints[] = new NotBlank();
            }
            if (isset($options['accept_minus']) && true === $options['accept_minus']) {
                $constraints[] = new Range([
                    'min' => $min,
                    'max' => $max,
                ]);
            } else {
                $constraints[] = new Range(['min' => 0, 'max' => $max]);
            }
            return $constraints;
        };
        $resolver->setDefaults(
            [
                'currency' => $currency,
                'scale' => $scale,
                'grouping' => true,
                'constraints' => $constraints,
                'accept_minus' => false, // マイナス値を許容するかどうか
            ]
        );
    }
    /**
     * {@inheritdoc}
     */
    public function getParent()
    {
        return MoneyType::class;
    }
    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'price';
    }
}
カテゴリー
EC-CUBE

【EC-CUBE4】EC-CUBE2からデータ移行

バージョンは「2.11」からの結構カスタマイズされているサイトの移行となります。

どのように移行する?

特にDBの項目増やしたり、プラグインを入れたりしてなければ、データ移行プラグインで最低限移行できる。

データ構成を把握していて、SQLで変換スクリプトを作成して移行しても問題はない。

今回は、データ移行プラグインをカスタマイズして対応した。

2系のデータが多すぎてdumpで落ちる場合

サーバーのスペックにもよるが、dtb_order_detailで5万件以上+その他諸々でサーバー落ち

結局テーブル毎に一覧を取得し、データ移行プラグイン用にcsvを作成し対応

受注明細の消費税のズレ

税率が基本情報で設定されるので、受注明細に設定されているものを優先するように変更

app/Plugin/DataMigration4/Controller/Admin/ConfigController.php

PHP
// 2.4.4, 2.11, 2.12
if (isset($this->baseinfo) && !empty($this->baseinfo)) {
    // $value['tax_rate'] = $data['tax_rate'] = $this->baseinfo['tax'];
    // 商品別税率設定へ対応_dtb_order_detailの[tax_rule]があればそのまま移行
    $value['tax_rate'] = $data['tax_rate'] ? $data['tax_rate'] : $this->baseinfo['tax'];
    $data['point_rate'] = $this->baseinfo['point_rate'];
}

商品別税率機能

2系から移行すると「dtb_tax_rule」テーブルのproduct_class_idが設定されていないので、うまく表示できない

対応方法

商品規格を使用していなければ、productに対してproduct_classが1つなので、対象を取得してアップデートする

カスタムするコードのヒント

app/Customize/Repository/ProductClassRepository.phpへメソッドの追加

PHP
public function getTranslationData()
{
    $qb = $this->createQueryBuilder('pc');
    $qb->select('IDENTITY(pc.Product) AS product_id, pc.id AS product_class_id');
    return $qb->getQuery()->getArrayResult();
}

app/Plugin/DataMigration4/Controller/Admin/ConfigController.php

作成したメソッド呼び出して、加工して当て込む

PHP
/** @var array $productData */
$productData = $this->productClassRepository->getTranslationData();
$this->productIdProductClassId = array_column($productData, 'product_class_id', 'product_id');

if ($data['product_id'] != 0) {
    $productId = $data['product_id'];
    if (isset($this->productIdProductClassId[$productId])) {
        $value['product_class_id'] = $this->productIdProductClassId[$productId];
    }
}

改行コード

app/Plugin/DataMigration4/Controller/Admin/ConfigController.php

PHP
/**
 * 改行コードを変換
 */
public function convertToNewlineCode($staring) {
    $convert = [
        '\r' => "\r",
        '\n' => "\n",
    ];
    
    $target = array_keys($convert);
    $replace = array_values($convert);
    
    return str_replace($target, $replace, $staring);
}

/* 下記のような項目は変換が必要な場合もある */
$data['main_list_comment'];
$data['main_comment'];
$data['comment3'];
$data['body'];
カテゴリー
EC-CUBE

【EC-CUBE4】初期注文ステータス変更

銀行支払いの場合は購入処理後に「新規受付」→「入金待ち」になるように変更する。

このカスタマイズは一例になります。

プラグインによっては、うまく胃開かない可能性があるのでご「注意ください。

もっと良いカスタマイズ方法があれば、コメントください。

[temp id=3]

購入フローの確認

購入フローは、使用する複数クラスで構成されています。

購入フローの設定は基本的に下記ファイルで行ってます。

ファイルパス:eccube/app/config/eccube/packages/purchaseflow.yaml

詳細は公式のドキュメントを確認ください。

EC-CUBE4開発者向けドキュメントサイト

OrderUpdateProcessorの確認

通常の購入フローで「新規受付」に変更しているのは、こちらのファイルです。

ファイルパス:eccube/src/Eccube/Service/PurchaseFlow/Processor/OrderUpdateProcessor.php

PHP
<?php

/*
 * This file is part of EC-CUBE
 *
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
 *
 * http://www.ec-cube.co.jp/
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Eccube\Service\PurchaseFlow\Processor;

use Eccube\Entity\ItemHolderInterface;
use Eccube\Entity\Master\OrderStatus;
use Eccube\Entity\Order;
use Eccube\Repository\Master\OrderStatusRepository;
use Eccube\Service\PurchaseFlow\PurchaseContext;

/**
 * 受注情報更新処理.
 */
class OrderUpdateProcessor extends AbstractPurchaseProcessor
{
    /**
     * @var OrderStatusRepository
     */
    private $orderStatusRepository;

    /**
     * OrderUpdateProcessor constructor.
     *
     * @param OrderStatusRepository $orderStatusRepository
     */
    public function __construct(OrderStatusRepository $orderStatusRepository)
    {
        $this->orderStatusRepository = $orderStatusRepository;
    }

    public function commit(ItemHolderInterface $target, PurchaseContext $context)
    {
        if (!$target instanceof Order) {
            return;
        }
        $OrderStatus = $this->orderStatusRepository->find(OrderStatus::NEW);
        $target->setOrderStatus($OrderStatus);
        $target->setOrderDate(new \DateTime());
    }
}

OrderUpdateProcessorの作成

本体の「OrderUpdateProcessor」をコピーしCustomizeでカスタマイズ

Payment::BANK_PAYMENTは、別途拡張して定数を追加したます。

PAID_WAIT_STATUSを配列にする事で、銀行振込以外でも入金待ちにしたい支払い方法追加可能としました。

PHP
<?php

namespace Customize\Service\PurchaseFlow\Processor;

use Eccube\Service\PurchaseFlow\Processor\AbstractPurchaseProcessor;
use Eccube\Entity\ItemHolderInterface;
use Customize\Entity\Master\OrderStatus;
use Customize\Entity\Payment;
use Eccube\Entity\Order;
use Eccube\Repository\Master\OrderStatusRepository;
use Eccube\Service\PurchaseFlow\PurchaseContext;


/**
 * 受注情報更新処理.
 */
class OrderUpdateProcessor extends AbstractPurchaseProcessor
{
    // 入金待ちにする注文ステータス
    const PAID_WAIT_STATUS = [
        Payment::BANK_PAYMENT,
    ];

    /**
     * @var OrderStatusRepository
     */
    private $orderStatusRepository;

    /**
     * OrderUpdateProcessor constructor.
     *
     * @param OrderStatusRepository $orderStatusRepository
     */
    public function __construct(OrderStatusRepository $orderStatusRepository)
    {
        $this->orderStatusRepository = $orderStatusRepository;
    }

    public function commit(ItemHolderInterface $target, PurchaseContext $context)
    {
        if (!$target instanceof Order) {
            return;
        }

        $Payment = $target->getPayment();

        // 支払い方法により、ステータスを変更する
        if (in_array($Payment->getId(), self::PAID_WAIT_STATUS, true)) {
            $OrderStatus = $this->orderStatusRepository->find(OrderStatus::PAID_WAIT);
        } else {
            $OrderStatus = $this->orderStatusRepository->find(OrderStatus::NEW);
        }
        $target->setOrderStatus($OrderStatus);
        $target->setOrderDate(new \DateTime());
    }
}

PurchaseFlowの設定変更

カスタマイズした「OrderUpdateProcessor」を読み込むように変更

Bash
services:

    # Purchase Flow for Cart

    eccube.purchase.flow.shopping.purchase:
        class: Doctrine\Common\Collections\ArrayCollection
        arguments:
            - #
                - '@Eccube\Service\PurchaseFlow\Processor\PreOrderIdValidator'
                - '@Eccube\Service\PurchaseFlow\Processor\PointProcessor'
                - '@Eccube\Service\PurchaseFlow\Processor\StockReduceProcessor'
                - '@Eccube\Service\PurchaseFlow\Processor\CustomerPurchaseInfoProcessor'
                # - '@Eccube\Service\PurchaseFlow\Processor\OrderUpdateProcessor'
                # 支払い方法で初回ステータスを変更できるようカスタマイズ
                - '@Customize\Service\PurchaseFlow\Processor\OrderUpdateProcessor'
カテゴリー
EC-CUBE

【EC-CUBE4】よく使うコマンド

[temp id=3]

使用頻度の高いコマンド

そのまま実行できないサーバーもあるので、「php」を追加しました。

キャッシュクリア

twigファイルの反映後やプロキシー更新後に使用する

Bash
php bin/console c:c --no-warmup

プロキシ作成・再構築

Bash
php bin/console eccube:generate:proxies

本体・カスタマイズ・プラグインのEntityをまとめている

下記フォルダに作成される

※4.1以上は削除してもコマンドで作り直せる

※4.0系は作り直されないので、ゴミ箱まで空にしない事
eccube/app/proxy/entity/src:src + customizeのentity
eccube/app/proxy/entity/app:pluginのEntity

マイグレーションコマンド

全ての実行されていないマイグレーション実行

Bash
php bin/console doctrine:migrations:migrate

空のマイグレーションファイル作成

Bash
php bin/console doctrine:migrations:generate

マイグレーションファイルを指定して実行

Bash
php bin/console doctrine:migrations:execute 20210324060716 --up
Bash
php bin/console doctrine:migrations:execute 20210324060716 --down

4.2からか上記では実行できなくなった。

Bash
php bin/console doctrine:migrations:execute 'DoctrineMigrations\Version20210324060716' --up
Bash
php bin/console doctrine:migrations:execute 'DoctrineMigrations\Version20210324060716' --down

スキーマ更新

EntityからDBへ反映

Bash
php bin/console doctrine:schema:update --dump-sql --force

4.2を使用していたら警告が発生

Bash
 [WARNING] Not passing the "--complete" option to "orm:schema-tool:update" is deprecated and will not be supported when 
           using doctrine/dbal 4

–completeオプションを追加して実行すると「doctrine_migration_versions」テーブル削除されます。

マイグレーション実行し直せば、復旧可能だが複数回実行して良いマイグレーションだけとは限らないので、くれぐれも注意してください!

カテゴリー
EC-CUBE

【EC-CUBE4】注文ステータス追加

[temp id=3]

コンビニ支払いのステータス追加

mtb_order_statusのid2が空いているので使用する

DB(テーブル)にデータ追加

  • mtb_order_status:入金待ち追加
  • mtb_order_status_color:入金待ちカラー追加
  • mtb_customer_order_status:入金待ち追加

OrderStatus(Entity)へ定数

本体ソースに追加しするか、継承クラスで定数を追加する

Traitには定数を追加できない。

定数「WAIT_PAYMENT」の追加

PHP
<?php
 namespace Customize\Entity;

 use Eccube\Entity\OrderStatus as BaseEntity;

 if (!class_exists('Customize\Entity\OrderStatus')) {

  /** 親クラスの定数宣言を拡張する目的に作成しています */
  class OrderStatus extends BaseEntity {
     /** 入金待ち */
     const WAIT_PAYMENT = 2;
     }
 }

注文ステータス遷移設定

app/config/packages/order_state_machine.php

編集箇所抜粋

PHP
'places' => [
     (string) Status::NEW,
     (string) Status::WAITI_PAYMENT,
     (string) Status::CANCEL,
     (string) Status::IN_PROGRESS,
     (string) Status::DELIVERED,
     (string) Status::PAID,
     (string) Status::PENDING,
     (string) Status::PROCESSING,
     (string) Status::RETURNED,
     (string) Status::CANCEL_ENTRY,
     (string) Status::CANCEL_PROCESSING
 ],

入金待ちから変更できるステータスの追加

※複数の場合は「配列で渡す」

PHP
'transitions' => [
    'pay' => [
        'from' => (string) Status::NEW,
        'from' => [(string) Status::NEW, (string) Status::WAIT_PAYMENT],
        'to' => (string) Status::PAID,
    ],
    'packing' => [
        'from' => [(string) Status::NEW, (string) Status::PAID, (string) Status::CANCEL_ENTRY],
        'to' => (string) Status::IN_PROGRESS,
    ],
    'cancel' => [
        'from' => [(string) Status::NEW, (string) Status::IN_PROGRESS, (string) Status::PAID, (string) Status::CANCEL_PROCESSING],
        'to' => (string) Status::CANCEL,
    ],
    'back_to_in_progress' => [
        'from' => (string) Status::CANCEL,
        'to' => (string) Status::IN_PROGRESS,
    ],
    'ship' => [
        'from' => [(string) Status::NEW, (string) Status::PAID, (string) Status::IN_PROGRESS, (string) Status::CANCEL_ENTRY],
        'to' => [(string) Status::DELIVERED],
    ],
    'return' => [
        'from' => (string) Status::DELIVERED,
        'to' => (string) Status::RETURNED,
    ],
    'cancel_return' => [
        'from' => (string) Status::RETURNED,
        'to' => (string) Status::DELIVERED,
    ],
    'cancel_entry' => [
        'from' => [(string) Status::NEW, (string) Status::IN_PROGRESS, (string) Status::PAID, (string) Status::DELIVERED, (string) Status::CANCEL_PROCESSING, (string) Status::WAIT_PAYMENT],
        'to' => (string) Status::CANCEL_ENTRY,
    ],
    'cancel_processing' => [
        'from' => (string) Status::CANCEL_ENTRY,
        'to' => (string) Status::CANCEL_PROCESSING,
    ],
],

OrderStateMachineでステータス変更処理

キャンセルの場合は、在庫やポイントを戻してくれる

PHP
$this->orderStateMachine->apply($Order, $changeStatus);

OrderStateMachineでステータスの処理を追加

在庫を戻したり、ポイントの減算処理も追加できる

PHP
    public static function getSubscribedEvents()
    {
        return [
            'workflow.order.completed' => ['onCompleted'],
            'workflow.order.transition.pay' => ['updatePaymentDate'],
            'workflow.order.transition.cancel' => [['rollbackStock'], ['rollbackUsePoint']],
            'workflow.order.transition.back_to_in_progress' => [['commitStock'], ['commitUsePoint']],
            'workflow.order.transition.ship' => [['commitAddPoint']],
            'workflow.order.transition.return' => [['rollbackUsePoint'], ['rollbackAddPoint']],
            'workflow.order.transition.cancel_return' => [['commitUsePoint'], ['commitAddPoint']],
            // 処理が必要であればここで定義
            // 'workflow.order.transition.cancel_entry' => [],
            // 'workflow.order.transition.cancel_processing' => [],
        ];
    }

決済処理中について

EC-CUBE4では、「決済処理中」ステータスがあり在庫を確保している状態です。

決済途中などで、エラーやサイトを閉じたりして発生しずっと受注が残るので、キャンセルして在庫の調整が必要になる。

カテゴリー
EC-CUBE

【EC-CUBE4】エラーリスト

エラーの解決方法をメモする。

ローカル環境構築関連

WARNING [cache] Failed to save key

事象1:DockerでカスタマイズしたCommand実行で発生した

事象2:AWSとソニーペイメントプラグインを使用していて、cron実行で発生した

原因:AWSのELBを挟んだ場合、REMOTE_ADDRで正しいIPが取得できない

プロジェクト名/app/Plugin/SlnPayment4/Service/Utill.php

修正前ソース

    public function GetLogInfo()
    {        
        $msg = '[{' . $_SERVER['SCRIPT_NAME'] . '}';
        
        $msg .= 'from {' . $_SERVER['REMOTE_ADDR'] . "}\n";
         ・
         ・
         ・

修正後ソース

    public function GetLogInfo()
    {        
        $msg = '[{' . $_SERVER['SCRIPT_NAME'] . '}';

        if(isset($_SERVER['REMOTE_ADDR'])){
            $msg .= 'from {' . $_SERVER['REMOTE_ADDR'] . "}\n";
        } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            // aws_cron対応
            $msg .= 'from {' . $_SERVER['HTTP_X_FORWARDED_FOR'] . "}\n";
        } else {
            // ローカル対応
            $msg .= 'from {' . '['.'localhost'.']' ."\t" . "}\n";
        }

ELB + K8S環境でingress-nginxを通した場合

$_SERVER['HTTP_X_ORIGINAL_FORWARDED_FOR']

参考サイト:https://uiuifree.com/blog/develop/php-elb-ip-address/

No composer.json present in the current directory, this may be the cause of the following exception.

事象:「docker-compose up -d」コマンド実行で発生

原因:composerのバージョンが対応していないのでエラーみたいです。

ファイル名:プロジェクト名/Dockerfile

修正後ソース

RUN curl -sS https://getcomposer.org/installer \
  | php \
  && mv composer.phar /usr/bin/composer \
  && composer selfupdate --1 \ ←これ!!!
  && composer config -g repos.packagist composer https://packagist.jp \
  && composer global require hirak/prestissimo \
  && chown www-data:www-data /var/www \
  && mkdir -p ${APACHE_DOCUMENT_ROOT}/var \
  && chown -R www-data:www-data ${APACHE_DOCUMENT_ROOT} \
  && find ${APACHE_DOCUMENT_ROOT} -type d -print0 \
  | xargs -0 chmod g+s \
  ;

Unable to replace alias “session.handler” with actual definition “session.handler.memcached”

事象:サーバーからPULLしたソースでローカル環境構築

原因:handler_idの設定値が正しくない(古い?)

ファイル名:プロジェクト名/app/config/eccube/packages/framework.yaml

修正後ファイル

framework:
    secret: '%env(ECCUBE_AUTH_MAGIC)%'
    default_locale: '%locale%'
    translator:
      fallback: ['%locale%']
    csrf_protection: { enabled: true }
    http_method_override: true
    trusted_hosts: ~
    # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
    session:
        # ↓ handler_id: session.handler.memcached ↓
        handler_id: 'Eccube\Session\Storage\Handler\SameSiteNoneCompatSessionHandler'
        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
        name: '%env(ECCUBE_COOKIE_NAME)%'
        cookie_lifetime: '%env(ECCUBE_COOKIE_LIFETIME)%'
        gc_maxlifetime: '%env(ECCUBE_GC_MAXLIFETIME)%'
        cookie_httponly: true

You have requested a non-existent parameter “kernel.secret”. Did you mean this: “kernel.charset”?

事象:サーバーからプルしたものをローカルで立ち上げようとしたら、発生したエラー

原因:パラメータが定義されているframework.yamlファイルがない

ファイル名:プロジェクト名/app/config/eccube/packages/framework.yaml

作成ファイル

framework:
    secret: '%env(ECCUBE_AUTH_MAGIC)%'
    default_locale: '%locale%'
    translator:
      fallback: ['%locale%']
    csrf_protection: { enabled: true }
    http_method_override: true
    trusted_hosts: ~
    # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
    session:
        handler_id: 'Eccube\Session\Storage\Handler\SameSiteNoneCompatSessionHandler'
        save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
        name: '%env(ECCUBE_COOKIE_NAME)%'
        cookie_lifetime: '%env(ECCUBE_COOKIE_LIFETIME)%'
        gc_maxlifetime: '%env(ECCUBE_GC_MAXLIFETIME)%'
        cookie_httponly: true

    # When using the HTTP Cache, ESI allows to render page fragments separately
    # and with different cache configurations for each fragment
    # https://symfony.com/doc/current/book/http_cache.html#edge-side-includes
    esi: { enabled: true }
    fragments: { enabled: true }
    php_errors:
        log: true
    assets:
      base_path: '/html/template/%eccube.theme%'
      packages:
        admin:
          base_path: '/html/template/admin'
        save_image:
          base_path: '/html/upload/save_image'
        plugin:
          base_path: '/html/plugin'
        install:
          base_path: '/html/template/install'
        temp_image:
          base_path: '/html/upload/temp_image'
        user_data:
          base_path: '/html/user_data'
        # json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
    cache:
        # this value is used as part of the "namespace" generated for the cache item keys
        # to avoid collisions when multiple apps share the same cache backend (e.g. a Redis server)
        # See https://symfony.com/doc/current/reference/configuration/framework.html#prefix-seed
        prefix_seed: ec-cube
    # The 'ide' option turns all of the file paths in an exception page
    # into clickable links that open the given file using your favorite IDE.
    # When 'ide' is set to null the file is opened in your web browser.
    # See https://symfony.com/doc/current/reference/configuration/framework.html#ide
    ide: ~
    validation: { enable_annotations: true }
    templating: { engines: ['twig'] }

参考サイト

https://github.com/EC-CUBE/ec-cube/blob/4.0.5/app/config/eccube/packages/framework.yaml#L2

Place “2” is not valid for workflow “order”.

事象:注文ステータス追加で発生

原因:ステータス遷移の制御設定が必要

ファイル名:プロジェクト名/app/config/packages/order_state_machine.php

カテゴリー
EC-CUBE

【EC-CUBE4】SQL

便利そうなSQLをメモしていこうと思います。

調査用SQL

共通テンプレート適用

layout_idがないデータは、dtb_page_layoutで設定して確認

SQL
USE eccubedb;
SELECT 
 P.id page_id
 ,PL.layout_id
 ,L.layout_name
 ,P.page_name
 ,P.file_name
FROM  dtb_page P
LEFT JOIN dtb_page_layout PL ON P.id = PL.page_id
LEFT JOIN dtb_layout L ON PL.layout_id = L.id;

データ削除

注文データ関連削除SQL

実際に削除する際は、バックアップ取るなど必ず確認して下さい!

SQL
USE eccubedb;
SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE dtb_cart;
TRUNCATE TABLE dtb_cart_item;
TRUNCATE TABLE dtb_order;
TRUNCATE TABLE dtb_order_item;
TRUNCATE TABLE dtb_shipping;
-- ソニーペイメント
TRUNCATE TABLE plg_sln_order_payment_history;
TRUNCATE TABLE plg_sln_order_payment_status;
-- ソニーペイメント定期購入
TRUNCATE TABLE plg_sln_regular_order;
TRUNCATE TABLE plg_sln_regular_order_item;
TRUNCATE TABLE plg_sln_regular_shipping;
TRUNCATE TABLE plg_sln_regular_order_to_order;
SET FOREIGN_KEY_CHECKS = 1;

外部キー制約の無効化, 有効化

関連するデータがある場合は、削除する順序が正しくない場合はエラーになるので、こちらを入れてます。

SQL
SET FOREIGN_KEY_CHECKS = 0;
SET FOREIGN_KEY_CHECKS = 1;
カテゴリー
EC-CUBE

【EC-CUBE4】リポジトリー:Repository

[temp id=3]

ドキュメント読んで、手順通りに学んだわけではないので参考程度に見てください。

Repository拡張方法

基本的に本体のソースはアップデートで変更になる可能性があるので、拡張して使用するのが安全です。

新規Repositoryファイルを作成

ファイル保存場所:Customize\Repository\ProductRepository.php

・Repositoryファイル「ProductRepository.php」を作成

<?php

namespace Customize\Repository;

use Eccube\Repository\ProductRepository as BaseRepository;

class ProductRepository extends BaseRepository
{
    /**
     * 新規メソッドの作成
     *
     * @param null|array $ProductStatus 指定の商品ステータス
     *
     * @return Products[] 商品の配列の配列
     */
    public function getProductList($ProductStatus = null)
    {
        $qb = $this->createQueryBuilder('p');

        if ($ProductStatus) {
            $qb
                ->andWhere($qb->expr()->in('p.Status', ':Status'))
                ->setParameter('Status', $ProductStatus);
        }

        $Products = $qb->getQuery()->getResult();

        return $Products;
    }

}

・拡張するRepositoryをuse(読込み)

・extendsで拡張

本体Repositoryの拡張

・メソッド毎コピー

・必要ファイルの読込み&construct定義

設定ファイルに定義

設定ファイル:app/config/eccube/services.yaml

・新規Repositoryファイル「ProductRepository.php」を作成

・拡張するRepositoryを定義する

services:
     ・
     ・
     ・
    Customize\Repository\ProductRepository:
        decorates: Eccube\Repository\ProductRepository

データの更新がうまくいかない

カスタマイズ案件で「dtb_cart」「dtb_cart_item」に似たテーブルを作った時の事。

登録直後の「cart_item」が表示されない挙動になった。

・F5で更新すれば表示される

・DBには「cart」「cart_item」が登録されている

結論を言うとcartクラスを更新すれば問題なし!!

$this->entityManager->refresh($cart)

登録の流れ

cart登録

cart_item登録

cart_item登録後なのでcartに紐づいていないcart_itemがデータになっている。

cart情報を取得しなおせば解決

カテゴリー
EC-CUBE

【EC-CUBE4】エンティティ:Entity

Entityについて説明していきます。

Entityの定義

Entityとは、データを扱うクラスです。

本体Entity:ドキュメントルート/src/Eccube/Entity

基本的には、データベースの各テーブルを定義し使用していきます。

関連するテーブルの情報を定義し「スキーマ更新コマンド」でDBへ更新も可能です。

Bash
bin/console doctrine:schema:update --dump-sql --force

商品クラスで解説

テーブル定義

PHP
    /**
     * Product
     * ↓ これがないとクラス名でテーブルが作成される
     * @ORM\Table(name="dtb_product")
     * ↓ クラス階層ごとに1つのテーブルを作成します
     * @ORM\InheritanceType("SINGLE_TABLE")
     * ↓ テーブルの識別用カラム
     * @ORM\DiscriminatorColumn(name="discriminator_type", type="string", length=255)
     * ↓ サービスの依存関係がないならコールバックの設定をした方が良いとのこと
     * @ORM\HasLifecycleCallbacks()
     * ↓ Repositoryの読込み設定
     * @ORM\Entity(repositoryClass="Eccube\Repository\ProductRepository")
     */
    class Product extends \Eccube\Entity\AbstractEntity

カラム定義

PHP
         * @ORM\Column(name="id", type="integer", options={"unsigned":true})
         * @ORM\Column(name="name", type="string", length=255)
         * @ORM\Column(name="create_date", type="datetimetz")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="IDENTITY")
         * @ORM\OneToMany(targetEntity="Eccube\Entity\ProductImage", mappedBy="Product", cascade={"remove"})
         * @ORM\ManyToOne(targetEntity="Eccube\Entity\Member")
         * @ORM\JoinColumns({
         *   @ORM\JoinColumn(name="creator_id", referencedColumnName="id")
         * })

定数やメソッドも追加可能

PHP
// 編集不可ステータス
const UNEDITABLE_STATUS = [
    self::PENDING,
    self::PROCESSING,
    self::CREDIT_SLIP,
];

/**
 * 銀行支払いかチェック
 * @return bool
 */
 public function isBankPayment() {
  return $this->getId() == Payment::BANK_PAYMENT ? true:false;
}

データ登録の流れ

  1. 登録対象Entityの取得 $Product = new Product
  2. $Product->setName = ‘テスト商品’
  3. 永続化登録設定 $this->entityManager->persist($Product)
  4. 登録処理 $this->entityManager->flush($Product)

実際のDBへ登録されるタイミングは、エラーがなく正常終了してテンプレートにいくタイミング。

Entityをカスタマイズしたい場合

src配下のEntity本体ソースは、アプデで影響が出る可能性があるので、

カラムやメソッドを追加したい場合

CustomizeでTraitを使用し拡張する

この場合は、スキーマ更新コマンドでdtb_categoryへslugカラムが追加される。

更新手順↓

proxy更新→キャッシュクリア→スキーマ更新コマンド

PHP
<?php

namespace Customize\Entity;

use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation\EntityExtension;

/**
 * @EntityExtension("Eccube\Entity\Category")
 */
trait CategoryTrait
{
    /**
     * @var string
     * @ORM\Column(type="string", length=255, nullable=true)
     */
    private $slug;

    /**
     * @return string|null
     */
    public function getSlug(): ?string
    {
        return $this->slug;
    }

    /**
     * @param string|null $slug
     * @return $this
     */
    public function setSlug(?string $slug): self
    {
        $this->slug = $slug;

        return $this;
    }
}

定数を使用したい場合

元のEntityを継承して使用する

PHP
<?php

namespace Customize\Entity\Master;

use Eccube\Entity\Master\OrderStatus as BaseEntity;

if (!class_exists('Customize\Entity\Master\OrderStatus')) {

    /** 親クラスの定数宣言を拡張する目的に作成しています */
    class OrderStatus extends BaseEntity {

        /** 入金待ち */
        const PAY_WAIT = 2;

        // 編集不可ステータス
        const UNEDITABLE_STATUS = [
            self::PENDING,
            self::PROCESSING,
            self::CREDIT_SLIP,
        ];
    
    }
}

使用したいファイルで、useして使用する

use Customize\Entity\Master\OrderStatus;

既存の他のTrait読み込み

下記プログラムでgetFullName()が使用可能となる

PHP
<?php

namespace Customize\Entity;

use Eccube\Annotation\EntityExtension;
use Eccube\Entity\NameTrait;

/**
  * @EntityExtension("Eccube\Entity\Customer")
 */
trait CustomerTrait
{
    use NameTrait;
}

src/Eccube/Entity/NameTrait.php

PHP
<?php

namespace Eccube\Entity;

trait NameTrait
{
    public function getFullName()
    {
        return (string) $this->name01.' '.$this->name02;
    }

    public function getFullNameKana()
    {
        return (string) $this->kana01.' '.$this->kana02;
    }
}

以前ハマった対応があったので紹介します。

初期表示データ取得できない

カスタマイズ中に発生した問題です。

EC-CUBE4は基本的にDoctrineを使用しています。

データベースをオブジェクト化した物で、都度データベースにアクセスしないでデータの取得や加工が行えます。

なぜこの状態になったか?

取得できなかったデータ
※わかりやすくEC-CUBEデフォルトのテーブルで説明します。

要件:カートの情報をデータ登録直後に一覧ページに表示したい。

関連するテーブルは、dtb_cartとdtb_cart_itemの2種類です。

パターン1

  1. dtb_cartへEntityで登録+永続化+フラッシュ
  2. dtb_cart_itemへEntityで登録+永続化+フラッシュ
  3. findByでリスト取得して、テンプレートへ

パターン2

  1. dtb_cartへEntityで登録+永続化+フラッシュ
  2. dtb_cart_itemへEntityで登録+永続化+フラッシュ
  3. テンプレートでfindByしてリスト取得

上記でうまくいかなかったです。

結局なんだったかと言うと、

リレーションが貼ってあるので、cart→cart_itemの順で登録

cartは最新の登録データは持っているが、紐づくcart_itemは後から登録されるので

cartのEntityに、cart_itemの最新情報が紐づいていない状態

cartのEntityを更新する

PHP
$this->entityManager->refresh(XXX);
XXXは、更新するEntityオブジェクト 

$this->entityManager->refresh($cart);

これで解決!