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