メモ > 技術 > CMS: ECCube > カスタマイズ: 商品情報に項目を追加(既存のテーブルを変更しない方法)
カスタマイズ: 商品情報に項目を追加(既存のテーブルを変更しない方法)
前述の「商品情報に項目を追加」は既存のテーブルに項目が追加されるが、これは既存のテーブルを変更しない方法
具体的には「dtb_product_expand」というテーブルを追加し、「dtb_product」とOneToOneで関連付けてデータを保存する
ただしテーブルを変更しない代わりに、プログラムをあちこち変更する必要があるので一長一短ではある
※「expand」という接尾語は、他に適切なものがあるか引き続き考えたい
・「expand」は「面積を広げる」「家を広げる」「ビジネスを広げる」のニュアンスみたい。それなら「データ領域を広げる」ということで変では無いか
・意味的には「extend」の方がより良いかもしれないが、それはclassのextendと被って可読性が下がりそう。コードの検索もしづらい
また今回は「継承して作る」ではなく「付属データを置く」なので、exptendでもちょっと違うかもしれない
・「custom」はいいかと思ったが、コードの検索時に「customize」や「customer」がヒットするので微妙
・「カスタマイズ」の意味で「cst」という文字にしてもいいかもしれない
ECCubeデフォルトのテーブルには「dtb_」「mtb_」「plg_」の接頭語が付けられているので、それに合わせるという意味でも
ただし「ProductCst」というクラスを作るなら「ProductCstType」というクラスも作ることになり、省略文字が名前の末尾ではなく中ほどに入ることになるのは微妙
また「cst」を付けるなら接尾語ではなく接頭語の方が、ECCubeのテーブルのネーミングルールには合う
・「item」はいいかと思ったが、すでに「dtb_cart_item」「dtb_order_item」などがあるので独自カスタマイズ用のテーブルか否かの判断がしづらくなりそう
・単純な項目追加なので、「addition」はアリかもしれない
・ひとまずは「expand」のままにしておく
■Entityを作成
app\Customize\Entity\ProductExpand.php を作成
<?php
namespace Customize\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ProductExpand
*
* @ORM\Table(name="dtb_product_expand")
* @ORM\HasLifecycleCallbacks()
* @ORM\Entity
*/
class ProductExpand extends \Eccube\Entity\AbstractEntity
{
/**
* @var int
*
* @ORM\Column(name="product_id", type="integer", nullable=false, options={"unsigned":true, "comment"="商品ID"})
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $product_id;
/**
* @var string|null
*
* @ORM\Column(name="recommend_text", type="text", nullable=true, options={"comment"="おすすめポイント"})
*/
private $recommend_text;
/**
* @var \Eccube\Entity\Product
*
* @ORM\OneToOne(targetEntity="Eccube\Entity\Product", inversedBy="ProductExpand")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="product_id", referencedColumnName="id")
* })
*/
private $Product;
/**
* Get the value of product_id.
*
* @return int
*/
public function getProductId()
{
return $this->product_id;
}
/**
* Get the value of recommend_text.
*
* @return string|null
*/
public function getRecommendText()
{
return $this->recommend_text;
}
/**
* Set the value of recommend_text.
*
* @param string|null $recommend_text
*/
public function setRecommendText($recommend_text)
{
$this->recommend_text = $recommend_text;
}
/**
* Get the value of Product.
*
* @return \Eccube\Entity\Product
*/
public function getProduct()
{
return $this->Product;
}
/**
* Set the value of Product.
*
* @param \Eccube\Entity\Product $product
*/
public function setProduct(\Eccube\Entity\Product $product)
{
$this->Product = $product;
}
}
app\Customize\Entity\ProductTrait.php を作成
<?php
namespace Customize\Entity;
use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation\EntityExtension;
/**
* @EntityExtension("Eccube\Entity\Product")
*/
trait ProductTrait
{
/**
* @var \Customize\Entity\ProductExpand
*
* @ORM\OneToOne(targetEntity="Customize\Entity\ProductExpand", mappedBy="Product", cascade={"persist","remove"})
*/
private $ProductExpand;
/**
* Get ProductExpand.
*
* @return \Customize\Entity\ProductExpand|null
*/
public function getProductExpand()
{
return $this->ProductExpand;
}
/**
* Set ProductExpand.
*
* @param \Customize\Entity\ProductExpand|null $productExpand
*
* @return ProductTrait
*/
public function setProductExpand(\Customize\Entity\ProductExpand $productExpand)
{
$this->ProductExpand = $productExpand;
return $this;
}
/**
* Has ProductExpand.
*
* @return bool
*/
public function hasProductExpand()
{
if (null === $this->ProductExpand) {
return false;
}
return true;
}
}
作業したらキャッシュを削除する
$ php bin/console cache:clear --no-warmup
プロキシファイルを作成する
$ php bin/console eccube:generate:proxies
以下に、トレイトを認識させるためのプロキシファイルが作成される
app\proxy\entity\src\Eccube\Entity\Product.php
■テーブル定義を変更
$ php bin/console cache:clear --no-warmup
$ php bin/console doctrine:migrations:diff
Generated new migration class to "/var/www/html/app/DoctrineMigrations/Version20240904031302.php" from schema differences.
以下のファイルが作成される
app\DoctrineMigrations\Version20240904031302.php
<?php declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240904031302 extends AbstractMigration
{
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE dtb_product_expand (product_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT \'商品ID\', recommend_text LONGTEXT DEFAULT NULL COMMENT \'おすすめポイント\', PRIMARY KEY(product_id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE dtb_product_expand ADD CONSTRAINT FK_F821F8284584665A FOREIGN KEY (product_id) REFERENCES dtb_product (id)');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('DROP TABLE dtb_product_expand');
}
}
マイグレーションを実行すると、テーブルが作成される
$ php bin/console doctrine:migrations:status
$ php bin/console doctrine:migrations:migrate
■フォームを作成
以下のファイルを作成する
app\Customize\Form\Type\Admin\ProductExpandType.php
<?php
namespace Customize\Form\Type\Admin;
use Customize\Entity\ProductExpand;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints as Assert;
class ProductExpandType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product_id', HiddenType::class, [
'mapped' => false,
])
->add('recommend_text', TextareaType::class, [
'required' => false,
'constraints' => [
new Assert\Length(['max' => 100]),
],
]);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => ProductExpand::class,
]);
}
public function getBlockPrefix()
{
return 'admin_product_expand';
}
}
app\Customize\Form\Extension\ProductExtension.php
<?php
namespace Customize\Form\Extension;
use Customize\Form\Type\Admin\ProductExpandType;
use Eccube\Form\Type\Admin\ProductType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
class ProductExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('productExpand', ProductExpandType::class, [
'mapped' => true,
])
;
}
public function getExtendedType()
{
return ProductType::class;
}
}
■コントローラーを調整
src\Eccube\Controller\Admin\Product\ProductController.php
を複製して
app\Customize\Controller\Admin\Product\ProductController.php
を作成し、以下を調整する
// namespaceを修正
namespace Eccube\Controller\Admin\Product;
↓
namespace Customize\Controller\Admin\Product;
// use部分に追加
use Customize\Entity\ProductExpand;
// editメソッド内を修正
if (is_null($id)) {
$Product = new Product();
$ProductClass = new ProductClass();
$ProductStatus = $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
$Product
->addProductClass($ProductClass)
->setStatus;
$ProductClass
↓
if (is_null($id)) {
$Product = new Product();
$ProductClass = new ProductClass();
$ProductExpand = new ProductExpand();
$ProductStatus = $this->productStatusRepository->find(ProductStatus::DISPLAY_HIDE);
$Product
->addProductClass($ProductClass)
->setStatus($ProductStatus)
->setProductExpand($ProductExpand);
$ProductExpand->setProduct($Product);
$ProductClass
// editメソッド内を修正
$ProductStock = $ProductClass->getProductStock();
}
$ProductExpand = $Product->getProductExpand();
}
↓
$ProductStock = $ProductClass->getProductStock();
}
}
// editメソッド内を修正
if (!$has_class) {
$ProductClass = $form['class']->getData();
↓
if (!$has_class) {
$ProductClass = $form['class']->getData();
$ProductExpand = $form['productExpand']->getData();
■画面表示を調整
src\Eccube\Resource\template\admin\Product\product.twig
を複製して
app\template\admin\Product\product.twig
を作成し、以下を追加する
<div class="collapse show ec-cardCollapse" id="recommendText">
<div class="card-body">
<div class="row">
<div class="col-3">
<span>{{ 'admin.common.recommend_text'|trans }}</span>
</div>
<div class="col-9">
<div>
{{ form_widget(form.productExpand.recommend_text, {id: 'wysiwyg-area', attr : { rows : "8"} }) }}
{{ form_errors(form.productExpand.recommend_text) }}
</div>
</div>
</div>
</div>
</div>
src\Eccube\Resource\template\default\Product\detail.twig
を複製して
app\template\default\Product\detail.twig
を作成し、以下を追加する
{% if Product.ProductExpand.recommendText %}
<div class="ec-productRole__recommendText">
{{ include(template_from_string(Product.ProductExpand.recommendText)) }}
</div>
{% endif %}
■リソースファイルを調整
ラベルを正しく表示させるに、以下のリソースファイルに文言を追加する
app\Customize\Resource\locale\messages.ja.yaml
common.recommend_text: おすすめポイント
admin.common.recommend_text: おすすめポイント
app\Customize\Resource\locale\messages.en.yaml
common.recommend_text: Recommendation Points
admin.common.recommend_text: Recommendation Points
■キャッシュをクリア
最後に、キャッシュをクリアしておく
$ php bin/console cache:clear --no-warmup
これで商品におすすめポイント欄が追加される
■メモ
FormTypeについては以下が参考になりそう
FormTypeのカスタマイズ - < for EC-CUBE 4 Developers />
https://doc4.ec-cube.net/customize_formtype
商品コントローラーにおいて、引き続き「商品のコピー」「商品CSV出力」にも処理の追加が必要そう
プログラムをあちこち変更しているので、それ以外にも悪影響が無いかは要確認