今回は、EC-CUBEのフロント側 (ユーザがECサイトで買い物をする部分)のログインロジックを変更します。
想定
EC-CUBE4(Symfony3.4)を使用します。
ある程度EC-CUBE4やSymfonyに触れたことがある方向けとして、SymfonyとEC CUBE4の基礎は省略します。
EC CUBE標準のフロントログインロジックは、登録されたメールアドレスを用いて行います。
ここでは、カラムlogid_id (ログインID)
を追加して、ログインIDでログインできるようにすることが目的です。
DBの変更
Entityクラスの拡張
まずは、DBテーブルdtb_customerにlogin_id
というカラムを追加しましょう。
SymfonyのDBロジックのDoctrineでサポートされている、Entityクラスに対するTraitクラスを使用してカラム追加を行います。
Eccube\Entity\Customer
に対するCustomerTraitクラスを新規で作成しましょう。
<?php
namespace Customize\Entity;
use Doctrine\ORM\Mapping as ORM;
use Eccube\Annotation as Eccube;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Eccube\EntityExtension("Eccube\Entity\Customer")
*/
trait CustomerTrait
{
/**
* @ORM\Column(name="login_id", type="string", length=255, nullable=true, unique=true)
*/
private $login_id;
/**
* Set login_id.
*
* @param string $login_id
*
* @return Customer
*/
public function setLoginId($login_id)
{
$this->login_id = $login_id;
return $this;
}
/**
* Get login_id.
*
* @return string
*/
public function getLoginId()
{
return $this->login_id;
}
}
Entityクラスの記述をDBに反映する
作成後に、EC CUBEディレクトリの最も浅いところで下記のコマンドをそれぞれ実行しましょう。
Windows環境の方は、頭にphp
を付け足して実行してください。
$ bin/console eccube:generate:proxies
$ bin/console doctrine:schema:update --dump-sql --force
1つ目のコマンドは、Entityのproxyファイルを生成するコマンドです。
app/proxy/entity
内に、対応するEntityのプロキシファイルが作成されれば成功です。
今回は、app\proxy\entity\src\Eccube\Entity\Customer.php
が作成されます。
ファイルの中身は、Eccube\Entity\Customer
の記述の中で、Customize\Entitiy\CustomerTrait
が呼び出されるものとなります。
2つ目のコマンドでは、CustomerTraitにアノテーションで記載したSQLを実行する形となります。
下記の出力結果が表示されれば成功で、この場合dtb_customerにカラム追加が実行されます。
nullable=true
とすればDEFAULT NULL
としてカラム追加できます。
nullable=false
とすればNOT NULL
としてカラム追加できますが、login_id
追加時に値はまだNULLとなっているので、この場合では実行時にエラーとなります。
下記が表示されれば成功です。
The following SQL statements will be executed:
ALTER TABLE dtb_customer ADD login_id VARCHAR(255) DEFAULT NULL;
CREATE UNIQUE INDEX UNIQ_8298BBE35CB2E05D ON dtb_customer (login_id);
Updating database schema...
2 queries were executed
[OK] Database schema updated successfully!
ログインロジックの書き換え
security.yamlの変更
カラム追加ができたので次に移りましょう。
app\config\eccube\packages\security.yaml
を書き換えて、ログインロジックに用いるクラスを変更します。
providersでは、ECサイトの管理画面側とフロント画面側のログインロジックで使用しているクラスが記述されています。
ここでは、フロント側で後ほど使用するクラスを呼び出すよう追記しましょう。
providers:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
# In this example, users are stored via Doctrine in the database
# To see the users at src/App/DataFixtures/ORM/LoadFixtures.php
# To load users from somewhere else: https://symfony.com/doc/current/security/custom_provider.html
member_provider:
id: Eccube\Security\Core\User\MemberProvider
customer_provider:
# id: Eccube\Security\Core\User\CustomerProvider # 本来書いてあったロジック
id: Customize\Security\Core\User\CustomerProviderCustomized # 新たに呼び出すクラスを記述
次に、ログイン時にlogin_id
を使用することを明示化します。
動作上はlogin_email
でも支障ないのですが、書き換えましょう。
form_login:
check_path: mypage_login
login_path: mypage_login
csrf_token_generator: security.csrf.token_manager
default_target_path: homepage
# username_parameter: 'login_email' login_emailは使わずに、login_idを使用する
username_parameter: 'login_id'
password_parameter: 'login_pass'
use_forward: false
success_handler: eccube.security.success_handler
failure_handler: eccube.security.failure_handler
ログインロジッククラスの拡張
次に、フロント側で使用するログインロジッククラスを作成します。
<?php
namespace Customize\Security\Core\User;
use Eccube\Entity\Customer;
use Eccube\Entity\Master\CustomerStatus;
use Eccube\Repository\CustomerRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class CustomerProviderCustomized implements UserProviderInterface
{
/**
* @var CustomerRepository
*/
protected $customerRepository;
public function __construct(CustomerRepository $customerRepository)
{
$this->customerRepository = $customerRepository;
}
/**
* Loads the user for the given username.
*
* This method must throw UsernameNotFoundException if the user is not
* found.
*
* @param string $username The username
*
* @return UserInterface
*
* @throws UsernameNotFoundException if the user is not found
*/
public function loadUserByUsername($username)
{
$Customer = $this->customerRepository->findOneBy([
'login_id' => $username,
'Status' => CustomerStatus::REGULAR,
]);
if (null === $Customer) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return $Customer;
}
/**
* Refreshes the user.
*
* It is up to the implementation to decide if the user data should be
* totally reloaded (e.g. from the database), or if the UserInterface
* object can just be merged into some internal array of users / identity
* map.
*
* @return UserInterface
*
* @throws UnsupportedUserException if the user is not supported
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof Customer) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
}
return $this->loadUserByUsername($user->getLoginId());
}
/**
* Whether this provider supports the given user class.
*
* @param string $class
*
* @return bool
*/
public function supportsClass($class)
{
return Customer::class === $class;
}
}
ここでは、Eccube\Security\Core\User\CustomerProvider
を踏襲しつつ、メソッド2つ(loadUserByUsername
とrefreshUser
)
を変更しています。
CustomerProvider
を踏襲・オーバーライドして記述を少なくすることも可能ですが、今回は前述のようにUserProviderInterface
をimplementsで拡張する方式となっております。
メソッドloadUserByUsername
は、主にログイン時に使用するメソッドになります。
引数で受け取る$username
に対し、dtb_customer.login_id
を検索するように書き換えました。
メソッドrefreshUser
は、画面ロード時等に最新のユーザ情報をDBから取得します。
EC CUBEのデフォルトですと$this->loadUserByUsername($user->getUsername())
でメールアドレスを引数とするので、こちらもログインIDを使用するように書き換えました。
ログインテンプレートの書き換え
FormTypeの拡張
EC CUBEのMYページのログイン画面のFormTypeを書き換えます。
security.yaml
を変更したのと同様に、login_email
を削除してlogin_id
を追加します。
<?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\Form\Extension\Front;
use Eccube\Common\EccubeConfig;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Form\AbstractTypeExtension;
use Eccube\Form\Type\Front\CustomerLoginType;
class CustomerLoginTypeExtension extends AbstractTypeExtension
{
/**
* @var EccubeConfig
*/
protected $eccubeConfig;
/**
* @var AuthenticationUtils
*/
protected $authenticationUtils;
public function __construct(AuthenticationUtils $authenticationUtils, EccubeConfig $eccubeConfig)
{
$this->authenticationUtils = $authenticationUtils;
$this->eccubeConfig = $eccubeConfig;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->remove('login_email')
->add('login_id', TextType::class, [
'attr' => [
'max_length' => $this->eccubeConfig['eccube_stext_len'],
],
'constraints' => [
new Assert\NotBlank(),
],
'data' => $this->authenticationUtils->getLastUsername(),
]);
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return CustomerLoginType::class;
}
}
最大文字数と必須入力を指定し、'data' => $this->authenticationUtils->getLastUsername()
でログインエラー時に入力されたログインIDを返すようにしています。
Twigテンプレートの拡張
次にMYページのログイン画面のTwigテンプレートを拡張しましょう。
src/Eccube/Resource/template/default/Mypage/login.twig
をapp/template/default/Mypage/
以下にコピーすることで、app
以下のものが反映されるようになります。
{% block main %}
<div class="ec-role">
<div class="ec-pageHeader">
<h1>{{ 'common.login'|trans }}</h1>
</div>
<div class="ec-off2Grid">
<div class="ec-off2Grid__cell">
<form name="login_mypage" id="login_mypage" method="post" action="{{ url('mypage_login') }}">
{% if app.session.flashBag.has('eccube.login.target.path') %}
{% for targetPath in app.session.flashBag.get('eccube.login.target.path') %}
<input type="hidden" name="_target_path" value="{{ targetPath }}" />
{% endfor %}
{% endif %}
<div class="ec-login">
<div class="ec-login__icon">
<div class="ec-icon"><img src="{{ asset('assets/icon/user.svg') }}" alt=""></div>
</div>
<div class="ec-login__input">
<div class="ec-input">
{{ form_widget(form.login_id, {'attr': {'style' : 'ime-mode: disabled;', 'placeholder' : 'ログインID', 'autofocus': true}}) }}
{# login_emailをコメントアウトする #}
{# {{ form_widget(form.login_email, {'attr': {'style' : 'ime-mode: disabled;', 'placeholder' : 'common.mail_address', 'autofocus': true}}) }} #}
{{ form_widget(form.login_pass, {'attr': {'placeholder' : 'common.password' }}) }}
</div>
最後に
ログインIDの発行については、要件等に応じて別途下記のような実装が必要になるかと思います。
- EC会員登録時に、会員自らがユニークなログインIDの入力を可能とする。
- EC会員登録時に、ユニークなログインIDを生成する
- etc..
何か間違った記述がある場合はコメント等にてご指摘等お願いします。
参考文献
[Symfony公式 ver3.4]How to Build a Traditional Login Form
https://symfony.com/doc/3.4/security/form_login_setup.html
SymfonyでGuardとEntityを使った認証 (@asaokamei様) (Quita)
https://qiita.com/asaokamei/items/5122e398e4cc17c84429
[EC CUBE公式]Entityのカスタマイズ
https://doc4.ec-cube.net/customize_entity