これまでの記事で、管理画面の商品登録に新メニュー【メーカー管理】を追加し、そこから新しくメーカーを追加できるようにカスタマイズしました。
今回はメーカーの新規登録に加え、保存済メーカーを一覧から選んで編集したり、削除したりする方法を紹介します。
デバッグモードを設定しておくと、エラーが起きたときに詳細情報が表示されるようになります。
エラー箇所を探しやすくなるので、開発前に設定しておくのをオススメします。
デバッグモードの設定方法については 以下記事 で解説しています。
カスタマイズ後は、デバッグモードの解除を忘れないように。
カスタマイズの流れ
- Repositoryの修正(削除メソッドの追加)
- Controllerの修正(修正&削除のためのコードを追加)
- Twigテンプレートの修正(修正&削除ボタン、修正フォーム、削除モーダルの追加)
Repositoryに削除メソッドを追加する
保存済のメーカー情報を削除するため、前回作成したMakerRepository
にdelete()
メソッドを定義します。
今回は、デフォルトで用意されているClassNameRepository
(src/Eccube/Repository)をベースに修正しました。
修正したファイルは「app/Customize/Repository」にアップします。
<?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 Customize\Repository;
use Doctrine\Persistence\ManagerRegistry as RegistryInterface;
use Customize\Entity\Maker;
use Eccube\Repository\AbstractRepository;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
/**
* MakerRepository
*/
class MakerRepository extends AbstractRepository
{
/**
* MakerRepository constructor.
*
* @param RegistryInterface $registry
*/
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Maker::class);
}
/**
* メーカーを保存する.
*
* @param Maker $maker
*/
public function save($maker)
{
$em = $this->getEntityManager();
$em->persist($maker);
$em->flush();
}
/**
* メーカーを削除する.
*
* @param Maker $maker
*
* @throws ForeignKeyConstraintViolationException 外部キー制約違反の場合
* @throws DriverException SQLiteの場合, 外部キー制約違反が発生すると, DriverExceptionをthrowします.
*/
public function delete($maker)
{
$em = $this->getEntityManager();
$em->remove($maker);
$em->flush();
}
}
ちなみに、保存済メーカー情報の編集にはsave()
メソッドを利用できるので、特別なメソッドは不要です。
Controllerを修正する
次に、前回作成したMakerController
を修正します。
こちらも、デフォルトで用意されているClassNameController
(src/Eccube/Controller/Admin/Product)をベースに修正しました。
- 編集用フォームと編集処理の追加
- 保存済メーカーの数だけフォームと処理が必要なため、foreachを使って繰り返し処理を行っています。
- 削除用の
delete()
メソッドを追加try
とcatch
を使って、削除が失敗した場合にはエラーメッセージが表示されるように実装しています。
修正したファイルは「app/Customize/Controller/Admin/Product」にアップします。
<?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 Customize\Controller\Admin\Product;
use Eccube\Controller\AbstractController;
use Customize\Entity\Maker;
use Customize\Form\Type\Admin\MakerType;
use Customize\Repository\MakerRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;
class MakerController extends AbstractController
{
/**
* @var MakerRepository
*/
protected $makerRepository;
/**
* MakerController constructor.
*
* @param MakerRepository $makerRepository
*/
public function __construct(MakerRepository $makerRepository)
{
$this->makerRepository = $makerRepository;
}
/**
* @Route("/%eccube_admin_route%/product/maker", name="admin_product_maker")
* @Template("@admin/Product/maker.twig")
*/
public function index(Request $request)
{
$makers = $this->makerRepository->findAll();
$newMaker = new Maker();
$builder = $this->formFactory
->createBuilder(MakerType::class, $newMaker);
$form = $builder->getForm();
/**
* 編集用フォーム
*/
$forms = [];
foreach ($makers as $maker) {
$id = $maker->getId();
$forms[$id] = $this->formFactory->createNamed('maker_'.$id, MakerType::class, $maker);
}
/**
* 新規登録処理
*/
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->makerRepository->save($newMaker);
$this->addSuccess('admin.common.save_complete', 'admin');
return $this->redirectToRoute('admin_product_maker');
}
}
/*
* 編集処理
*/
foreach ($forms as $editForm) {
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$this->makerRepository->save($editForm->getData());
$this->addSuccess('admin.common.save_complete', 'admin');
return $this->redirectToRoute('admin_product_maker');
}
}
$formViews = [];
foreach ($forms as $key => $value) {
$formViews[$key] = $value->createView();
}
return [
'form' => $form->createView(),
'makers' => $makers,
'newMaker' => $newMaker,
'forms' => $formViews,
];
}
/**
* @Route("/%eccube_admin_route%/product/maker/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_maker_delete", methods={"DELETE"})
*/
public function delete(Request $request, Maker $maker)
{
$this->isTokenValid();
try {
$this->makerRepository->delete($maker);
$this->addSuccess('admin.common.delete_complete', 'admin');
} catch (\Exception $e) {
$message = trans('admin.common.delete_error_foreign_key', ['%name%' => $maker->getName()]);
$this->addError($message, 'admin');
}
return $this->redirectToRoute('admin_product_maker');
}
}
Twigテンプレートを修正する
最後に、前回作成したTwigテンプレートを修正します。
これも、デフォルトで用意されているclass_name.twig
(src/Eccube/Resource/template/admin/Product)をベースに修正しました。
- 編集および削除ボタン(アイコン)を設置します。
- 削除モーダルを設置します。
- 編集および削除ボタン(アイコン)や、決定/キャンセルボタンをクリックしたときの処理を、JavaScript(JQuery)で記述します。
修正したファイルは「app/template/admin/Product」にアップします。
{#
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.
#}
{% extends '@admin/default_frame.twig' %}
{% set menus = ['product', 'maker'] %}
{% block title %}{{ 'admin.product.maker_management'|trans }}{% endblock %}
{% block sub_title %}{{ 'admin.product.product_management'|trans }}{% endblock %}
{% form_theme form '@admin/Form/bootstrap_4_horizontal_layout.html.twig' %}
{% block stylesheet %}
<style type="text/css">
.list-group-item:hover {
z-index: inherit;
}
</style>
{% endblock stylesheet %}
{% block javascript %}
<script>
$(function() {
// 編集
$('.sortable-item').on('click', 'a.action-edit', function(e) {
e.preventDefault();
var current = $(this).parents('li');
current.find('.mode-view').addClass('d-none');
current.find('.mode-edit').removeClass('d-none');
});
// 編集キャンセル
$('.sortable-item').on('click', 'button.action-edit-cancel', function(e) {
location.href = '{{ url('admin_product_maker') }}';
});
// 編集時, エラーがあれば入力欄を表示.
$('.sortable-item').find('.is-invalid').each(function(e) {
var current = $(this).parents('li');
current.find('.mode-view').addClass('d-none');
current.find('.mode-edit').removeClass('d-none');
});
// 削除モーダルのhrefとmessageの変更
$('#DeleteModal').on('shown.bs.modal', function(event) {
var target = $(event.relatedTarget);
// hrefの変更
$(this).find('[data-method="delete"]').attr('href', target.data('url'));
// messageの変更
$(this).find('p.modal-message').text(target.data('message'));
});
});
</script>
{% endblock %}
{% block main %}
<div class="c-contentsArea__cols">
<div class="c-contentsArea__primaryCol">
<div class="c-primaryCol">
<div class="card rounded border-0 mb-4">
<div class="card-body p-0">
<div class="card rounded border-0">
<ul class="list-group list-group-flush sortable-container">
<li class="list-group-item">
<form role="form" class="row" name="form1" id="form1" method="post" action="{{ url('admin_product_maker') }}">
<div class="col-auto align-self-center"><span>{{ 'admin.product.maker.name'|trans }}</span></div>
<div class="col-3 me-2">
{{ form_widget(form._token) }}
{{ form_widget(form.name) }}
{{ form_errors(form.name) }}
</div>
<div class="col-auto align-self-center" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ 'tooltip.product.backend_name'|trans }}">
<span>{{ 'admin.product.maker.code'|trans }}</span>
</div>
<div class="col-3">
{{ form_widget(form.code) }}
{{ form_errors(form.code) }}
</div>
<div class="col-auto">
<button class="btn btn-ec-regular" type="submit">{{ 'admin.common.create__new'|trans }}</button>
</div>
</form>
</li>
<li class="list-group-item">
<div class="row">
<div class="col-auto"><strong> </strong></div>
<div class="col-auto"><strong>{{ 'admin.common.id'|trans }}</strong></div>
<div class="col-1"><strong>{{ 'admin.product.maker_management'|trans }}</strong></div>
</div>
</li>
{% for maker in makers %}
<li id="ex-class_name-{{ maker.id }}" class="list-group-item sortable-item" data-class-name-id="{{ maker.id }}">
<div class="row justify-content-around mode-view">
<div class="col-auto d-flex align-items-center"><i class="fa fa-bars text-ec-gray"></i></div>
<div class="col-auto d-flex align-items-center">{{ maker.id }}</div>
<div class="col d-flex align-items-center">{{ maker.name }}</div>
{# 編集アイコンと削除アイコンを設置 #}
<div class="col-auto text-end">
<a class="btn btn-ec-actionIcon me-2 action-edit" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ 'admin.common.edit'|trans }}">
<i class="fa fa-pencil fa-lg text-secondary"></i>
</a>
<div class="d-inline-block me-2" data-bs-toggle="tooltip"
data-bs-placement="top" title="{{ 'admin.common.delete'|trans }}">
<a class="btn btn-ec-actionIcon" data-bs-toggle="modal" data-bs-target="#DeleteModal" data-url="{{ url('admin_product_maker_delete', {'id' : maker.id}) }}" data-message="{{ 'admin.common.delete_modal__message'|trans({ "%name%" : maker.name }) }}">
<i class="fa fa-close fa-lg text-secondary"></i>
</a>
</div>
</div>
</div>
{# 編集用のフォームを設置 #}
<form class="row d-none mode-edit" method="post" action="{{ url('admin_product_maker') }}">
{{ form_widget(forms[maker.id]._token) }}
<div class="col-auto align-self-center"><span>{{ 'admin.product.maker.name'|trans }}</span></div>
<div class="col-auto align-items-center">
{{ form_widget(forms[maker.id].name, {'attr': {'data-origin-value': forms[maker.id].name.vars.value}}) }}
{{ form_errors(forms[maker.id].name) }}
</div>
<div class="col-auto align-self-center"><span>{{ 'admin.product.maker.code'|trans }}</span></div>
<div class="col-auto align-items-center">
{{ form_widget(forms[maker.id].code, {'attr': {'data-origin-value': forms[maker.id].code.vars.value}}) }}
{{ form_errors(forms[maker.id].code) }}
</div>
<div class="col-auto align-items-center">
<button class="btn btn-ec-conversion" type="submit">{{ 'admin.common.decision'|trans }}</button>
</div>
<div class="col-auto align-items-center">
<button class="btn btn-ec-sub action-edit-cancel" type="button">{{ 'admin.common.cancel'|trans }}</button>
</div>
</form>
</li>
{% endfor %}
</ul>
<!-- 削除モーダル -->
<div class="modal fade" id="DeleteModal" tabindex="-1" role="dialog"
aria-labelledby="DeleteModal" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold">
{{ 'admin.common.delete_modal__title'|trans }}
</h5>
<button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close">
</button>
</div>
<div class="modal-body text-start">
<p class="text-start modal-message"><!-- jsでメッセージを挿入 --></p>
</div>
<div class="modal-footer">
<button class="btn btn-ec-sub" type="button" data-bs-dismiss="modal">
{{ 'admin.common.cancel'|trans }}
</button>
<a class="btn btn-ec-delete" href="#" {{ csrf_token_for_anchor() }}
data-method="delete" data-confirm="false">
{{ 'admin.common.delete'|trans }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
キャッシュを削除
ここまでで準備完了です。
管理画面からキャッシュを削除して、商品管理メニューのメーカー管理を確認しましょう。
【メーカー管理】の右端に、「編集」「削除」ができるアイコンが表示され、保存済メーカーの編集・削除ができるようになりました。
まとめ&次のステップ
以上、新メニューからデータを追加・編集・削除できるようにするカスタマイズ方法を紹介しました。
ここまでで基本的な機能は実装できましたが、まだソート機能(並び替え機能)や、メーカーの個々のページは実装できていません。
引き続き、今後の記事で紹介していくつもりですので、ぜひ本記事と合わせてご覧ください。