目標
- 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