LoginSignup
10
14

More than 1 year has passed since last update.

EC-CUBE4 エンティティ拡張で既存メソッドを書き換える

Last updated at Posted at 2018-10-18

目標

  • CustomerエンティティのgetRoles()メソッドで返す値をカスタマイズする。
  • 動作確認バージョンは2018/10/18時点で最新の4.0.0。

失敗例

http://doc4.ec-cube.net/customize_entity
ドキュメントに従ってエンティティを拡張する。

app/Customize/Entity/CustomerTrait.php
<?php
namespace Customize\Entity;

use Eccube\Annotation as Eccube;

/**
 * @Eccube\EntityExtension("Eccube\Entity\Customer")
 */
trait CustomerTrait
{
    public function getRoles()
    {
        return ['ROLE_USER', 'ROLE_HOGE'];
    }
}
$ php bin/console eccube:generate:proxies
gen -> /var/www/html/app/proxy/entity/Customer.php

出来上がったエンティティ

app/proxy/entity/Customer.php
// 前略
    class Customer extends \Eccube\Entity\AbstractEntity implements UserInterface
    {
    use \Customize\Entity\CustomerTrait;
// 中略
        public function getRoles()
        {
            return ['ROLE_USER'];
        }
//後略
var_export((new \Eccube\Entity\Customer())->getRoles());
// array (
//   0 => 'ROLE_CUSTOMER',
// )

トレイトで同名のメソッドを定義しても、CustomerクラスのgetRole()で上書きされてしまう。

そもそも

EC-CUBEはどのようにプロキシファイルを生成しているのか?
https://github.com/EC-CUBE/ec-cube/blob/4.0.0/src/Eccube/Service/EntityProxyService.php

→対象となるトレイトをEntityProxyServiceで字句解析してuse句にセットしてる。
→だったらDIでサービスを置き換えて字句解析で上書きしちゃえばよくない?

成功例

app/Customize/Entity/CustomerTrait.php
<?php
// 前略
    public function getTraitRoles() // 関数名を変更
// 後略
app/config/eccube/services.yaml
# ファイル末尾
    Customize\Service\EntityProxyService:
        decorates: Eccube\Service\EntityProxyService
        arguments:
        - '@doctrine.orm.default_entity_manager'
        - '@Customize\Service\EntityProxyService.inner'
app/Customize/Service/EntityProxyService.php
<?php
namespace Customize\Service;

use Eccube\Service\EntityProxyService as BaseService;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
use Symfony\Component\Console\Output\OutputInterface;

class EntityProxyService extends BaseService
{
    /**
     * @var BaseService
     */
    private $baseService;

    public function __construct(EntityManagerInterface $entityManager, BaseService $baseService)
    {
        parent::__construct($entityManager);
        $this->baseService = $baseService;
    }

    public function generate($includesDirs, $excludeDirs, $outputDir, OutputInterface $output = null)
    {
        // 生成されたプロキシファイル一覧
        $generatedFiles = $this->baseService->generate($includesDirs, $excludeDirs, $outputDir, $output);

        foreach ($generatedFiles as $generatedFile) {

            // 末尾が/Customer.phpなら
            if (preg_match('#/Customer.php$#', $generatedFile)) {
                $entityTokens = Tokens::fromCode(file_get_contents($generatedFile));
                $functionIndex = 0;
                // T_FUNCTIONを探す
                while(($functionIndex = $entityTokens->getNextTokenOfKind($functionIndex, [[T_FUNCTION]])) > 0) {

                    $nextIndex = $entityTokens->getNextMeaningfulToken($functionIndex);
                    $nextToken = $entityTokens[$nextIndex];

                    // T_FUNCTIONの次の空白以外のトークンがT_STRINGで、getRolesなら
                    if ($nextToken->isGivenKind(T_STRING) && $nextToken->getContent() === 'getRoles') {

                        // 次の'{'から対応する'}'まで
                        $startIndex = $entityTokens->getNextTokenOfKind($nextIndex, ['{']);
                        $endIndex = $entityTokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $startIndex);

                        // を'{return $this->getTraitRoles();}'で置き換える
                        $newTokens = Tokens::fromCode('{return $this->getTraitRoles();}');
                        $entityTokens->overrideRange($startIndex, $endIndex, $newTokens);
                        $code = $entityTokens->generateCode();
                        file_put_contents($generatedFile, $code);
                        break;
                    }
                }
            }
        }

        return $generatedFiles;
    }
}
$ php bin/console eccube:generate:proxies
gen -> /var/www/html/app/proxy/entity/Customer.php

出来上がったエンティティ

app/proxy/entity/Customer.php
// 前略
        public function getRoles()
        {return $this->getTraitRoles();}
//後略
var_export((new \Eccube\Entity\Customer())->getRoles());
// array (
//   0 => 'ROLE_CUSTOMER',
//   1 => 'ROLE_HOGE',
// )

なんでもできそう。

元に戻すときの注意点

一度プロキシファイルを作ると、次回からはプロキシファイルをベースに再生成する。
EntityProxyServiceを元に戻しても、getRolesの中身は戻らない。
かといっていきなりプロキシファイルを消して再生成すると、プロキシファイルがないと言われ怒られる。
必ずキャッシュを消してから再生成すること。

$ bin/console cache:clear --no-warmup
$ rm app/proxy/entity/Customer.php
$ php bin/console eccube:generate:proxies
10
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
14