『述べて作らず、信じて古を好む。
窃に老彭に比す』

論語より

EC Cubeプラグイン開発、調査いったんまとめ。

ここ数週間、EC Cubeでのプラグイン開発について
調べていて、かなり全体像がつかめてきたので、一度まとめようと思います。


(参考:購入フローのカスタマイズ)

 

まず、総評として…、

ECCube、めっちゃいいっすね!!!

 

なにって、カスタマイズがしやすいように
つくられてるし、

公式ドキュメントもキレイで分かりやすいし、

細かい手心を感じるし、

さすが日本製! 日本でこんな
素晴らしいシステムが開発されてたなんて!

感動しました。

 

オープンソースなので、独自の処理をいれたかったら、

ある程度は、オリジナルのソースに
手を入れないといけないのかな…

とか思ったり、

既存のプラグインに、独自の処理をいれたかったら、

そのプラグインのソースに
手を入れないといけないのかな…

と思ってたのですが、

いやいやいや、、
ちゃんとカスタマイズの口が用意されてて

独自のプラグインをつくれば、
そこからカスタマイズできるじゃないですか!(ハート)

という素晴らしさ。

 

ありがとう、日本。

ということで、解説いきます。

①項目の追加

Entityへの項目の追加ができます。

例えば、trait ProductTrait を実装することで、
EC Cube標準のProductクラスはそのままで、項目の追加・拡張ができます。

<?php
namespace Plugin\matsui\Entity;

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

use Eccube\Annotation\EntityExtension;

/**
* @EntityExtension("Eccube\Entity\Product")
*/

trait ProductTrait
{
 /**
 * @ORM\Column(name="can_skip_first_shipping", type="boolean", nullable=true)
 * @Eccube\FormAppend
 */
 public $can_skip_first_shipping;

 public function getCanSkipFirstShipping()
 {
  return $this->can_skip_first_shipping;
 }

 public function setCanSkipFirstShipping($can_skip_first_shipping)
 {
   $this->can_skip_first_shipping = $can_skip_first_shipping;
   return $this;
 }
}
?>

詳しくは、こちら。
>> Entityのカスタマイズ

 

今回は、Productに注文項目の有無フラグを追加して、
その情報を、商品詳細 → カート → 注文 への渡しているので、

ProductTrait、CartItemTrait、OrderItemTraitを作っています。

 

②画面への入力項目の追加

EC Cubeには、処理の各所にEventListenerが用意されており、
そこに、好きな処理を挟みこむことができます。

UIをいじりたい場合や、
とある処理の後にカスタマイズを入れたい場合などに、便利です。

今回の僕の開発の場合は、

・商品詳細画面のTwig描写前
・カート商品の追加処理完了後

に処理を追加しています。

<?php

namespace Plugin\matsui\EventListener\EventSubscriber\Front\Product;

use Eccube\Entity\Master\SaleType;
use Eccube\Entity\Product;
use Eccube\Event\TemplateEvent;
use Eccube\Repository\Master\SaleTypeRepository;
use Eccube\Service\CartService;
use Eccube\Event\EccubeEvents;
use Eccube\Event\EventArgs;

use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class AddRegularCycleOption implements EventSubscriberInterface
{
 /**
  * @var \Doctrine\ORM\EntityManagerInterface
  */
 protected $entityManager;

 /**
  * @var SaleTypeRepository
  */
 private $saleTypeRepository;

 /**
  * @var CartService
  */
 protected $cartService;

 public function __construct(
  EntityManagerInterface $entityManager,
  SaleTypeRepository $saleTypeRepository,
  CartService $cartService
 ) {
  $this->entityManager = $entityManager;
  $this->saleTypeRepository = $saleTypeRepository;
  $this->cartService = $cartService;
 }


 public static function getSubscribedEvents()
 {
  return [
   'Product/detail.twig' => 'detail',
   EccubeEvents::FRONT_PRODUCT_CART_ADD_COMPLETE => 'afterAddToCart', // カート追加処理完了後
  ];
 }

 /**
  * 商品詳細画面のテンプレートに定期商品オプションを追加する
  *
  * @param TemplateEvent $event
  */
 public function detail(TemplateEvent $event)
 {
  /** @var Product $Product */
  $Product = $event->getParameter('Product');
  $sale_type_flg = false;
  if ($Product->getProductClasses()->count() > 1) {
   foreach ($Product->getProductClasses() as $productClass){
    if($productClass->getSaleType()->getName() === '定期商品'){
     $sale_type_flg = true;
    }
   }
  }

  /** @var SaleType $SaleType */
  $SaleType = $this->saleTypeRepository->findOneBy([
   'name' => '定期商品',
  ]);

  if (is_null($SaleType)) {
   return;
  }

  $event->addSnippet('@matsui/front/Product/add_regular_cycle_option.twig');
 }


 /**
  * カートに商品を追加した後の処理
  *
  * @param EventArgs $event
  */
 public function afterAddToCart(EventArgs $event)
 {
  $addCartData = $event['form']->getData();
  foreach ($this->cartService->getCarts() as $Cart) {
   foreach ($Cart->getCartItems() as $CartItem) {
    if($CartItem->getProductClassId() != $addCartData['product_class_id']) continue;
    $CartItem->setIsSkipFirstShipping(
     $addCartData['is_skip_first_shipping']
    );
    $this->entityManager->flush();
   }
  }
 }
}

47行目、48行目で、処理の追加箇所を指定しています☝️

詳しくは、こちら。
>> Symfonyの機能を使ったカスタマイズ

 

 

あと、FormTypeを拡張することで、
画面で入力した値を、そのままデータベース保存まで持っていけます。

<?php

namespace Plugin\matsui\Form\Extension\Front;

use Eccube\Entity\Product;
use Eccube\Form\Type\AddCartType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;

class AddCartItemExtension extends AbstractTypeExtension
{
 /**
  * {@inheritdoc}
  */
 public function buildForm(FormBuilderInterface $builder, array $options)
 {
  $builder->addEventListener(
   FormEvents::PRE_SET_DATA,
   function (FormEvent $event) use ($options) {

    $Product = $options['product'];
    $form = $event->getForm();
    if($Product->getCanSkipFirstShipping()){
     $form->add('select_skip_first_shipping', ChoiceType::class, [
      'label' => '当月配送',
      'choices' => array_flip([
       'no_skip' => '当月配送・翌月1日配送',
       'skip' => '当月なし・翌月1日配送',
      ]),
      'mapped' => false,
     ]);
     $form->add('is_skip_first_shipping', HiddenType::class, [
      'data' => '0',
     ]);
    }
   }
  );

  $builder->addEventListener(
   FormEvents::PRE_SUBMIT,
   function (FormEvent $event) {
    $data = $event->getData();
    if(array_key_exists('select_skip_first_shipping', $data)) {
     $data['is_skip_first_shipping'] = $data['select_skip_first_shipping'] == 'skip';
     $event->setData($data);
    }
   }
  );
 }

 /**
  * {@inheritdoc}
  */
 public function getExtendedType()
 {
  return AddCartType::class;
 }
 
 /**
  * {@inheritdoc}
  */
 public static function getExtendedTypes(): iterable
 {
  yield AddCartType::class;
 }
}

詳しくはこちら。
>> FormTypeのカスタマイズ

 

ただ項目を増やすだけだと、画面の変な位置に入力欄が出るので、
そのへんは、👆のTwigでスクリプトタグからJavaScriptを実行するなど
して、うまく調整してください。(というか、しました。)

<script>
 $(function() {
  if($("#select_skip_first_shipping").length) {
   $(".ec-productRole__actions:first").prepend($("#admin_product_extension_1"));
   $("#admin_product_extension_1").append($("#select_skip_first_shipping").parent());
  }
 });
</script>

<div class="ec-select" id="admin_product_extension_1">
</div>

先ほど紹介した「商品詳細画面のTwig描写前」への処理の組み込みが
まさに、’Product/detail.twig’に、このTwigを追記する処理です。

 

なので、画面へのカスタマイズをしたい時は、

・入力項目は、FormTypeExtention
・デザイン追加は、追記TwigのHTML

で増やして、
表示位置は、Twig内のJSで調整することになるように思います。

 

③変換処理への上書き

EC Cubeでは、サービスの処理の上書きができます。
ありがたすぎです。

詳しくはこちら。
>> EC-CUBE4 Serviceクラスのカスタマイズ方法

 

今回は、CartItemに項目を増やしていたので、

OrderHelperクラスにカスタマイズを入れて、

CartItemに増やした項目の登録値が、
OrderItemにも渡されるように実装をしています。

 

④カートのバリデーションチェック追加

カート画面から注文手続きに移る際のバリデーションチェックに
カスタマイズを入れることができます。

詳しくはこちら。
>> 購入フローのカスタマイズ

 

ItemPreprocessorをカスタマイズしてます。

今回は、別の種類の商品が、同じ注文に混ざると困るんで、
混ざっていた場合は、注文画面へ遷移できないようにカスタマイズをしています。