-
Notifications
You must be signed in to change notification settings - Fork 718
fix: プラグイン/Customize 直下のバンドルでも Entity の redeclare fatal を防ぐ (auto_mapping の二重登録をコンパイル時に除去) #6982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nanasess
merged 5 commits into
EC-CUBE:4.4
from
nanasess:fix/6979-strip-auto-mapped-entity-paths
Jul 31, 2026
Merged
Changes from 2 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6d1324d
fix(doctrine): auto_mapping による Entity ディレクトリの二重登録をコンパイル時に解消
nanasess de67cc8
test(doctrine): 直下バンドル構成で boot まで到達する redeclare 回帰テストを追加
nanasess 789585c
style(test): rector の指摘に追随
nanasess 2e21149
test(doctrine): レビュー指摘に対応 (サブプロセス検証 + 既存ファイルの退避・復元)
nanasess 72ef3bc
fix(test): サブプロセス検証を専用 APP_ENV に隔離し var/cache/test の破壊を回避
nanasess File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
src/Eccube/DependencyInjection/Compiler/StripAutoMappedEntityPathsPass.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| /* | ||
| * 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 Eccube\DependencyInjection\Compiler; | ||
|
|
||
| use Doctrine\ORM\Mapping\Driver\AttributeDriver; | ||
| use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
| use Symfony\Component\DependencyInjection\Definition; | ||
| use Symfony\Component\DependencyInjection\Reference; | ||
|
|
||
| /** | ||
| * doctrine.orm.auto_mapping が生成する素の AttributeDriver から、 | ||
| * Kernel::addEntityExtensionPass が TraitProxyAttributeDriver で明示登録している | ||
| * Entity ディレクトリを取り除く. | ||
| * | ||
| * doctrine-bundle は auto_mapping 対象バンドルの Entity ディレクトリを | ||
| * 「バンドルクラスが置かれたディレクトリ + /Entity」で検出し (DoctrineExtension::detectMetadataDriver)、 | ||
| * 同じドライバ型のバンドルをすべて 1 つの AttributeDriver インスタンスに集約する | ||
| * (DoctrineExtension::registerMappingDrivers). そのため、EC-CUBE が明示登録している | ||
| * ディレクトリ (src/Eccube/Entity, app/Customize/Entity, app/Plugin/<Code>/Entity) が | ||
| * 素の AttributeDriver にも入り込む. | ||
| * | ||
| * 素のドライバは ColocatedMappingDriver::getAllClassNames() で Entity ソースを無条件に | ||
| * require_once するため、Kernel::loadEntityProxies() が app/proxy/entity の Proxy を | ||
| * 先にロードした状態では "Cannot redeclare class" で fatal になる | ||
| * (Entity の if (!class_exists()) ガード全廃前は、そのガードが吸収していた). | ||
| * | ||
| * MappingDriverChain は名前空間ごとに 1 ドライバしか保持しないため、EC-CUBE の明示登録で | ||
| * 上書きされたように見えるが、素のドライバが別の名前空間 (第三者バンドル) でチェーンに | ||
| * 残っていると、その getAllClassNames() が自身の全パスを走査して同じ fatal を引き起こす. | ||
| * | ||
| * バンドル名を列挙する (doctrine.orm.mappings.<Bundle>: false) 方式では、サードパーティ製 | ||
| * プラグインが持ち込むバンドル名を事前に知ることができないため、コンパイル時にパスを | ||
| * 取り除く方式とする. | ||
| * | ||
| * @see https://github.com/EC-CUBE/ec-cube/pull/6895 Entity の if(!class_exists()) ガード全廃 | ||
| * @see https://github.com/EC-CUBE/ec-cube/issues/6979 | ||
| */ | ||
| final readonly class StripAutoMappedEntityPathsPass implements CompilerPassInterface | ||
| { | ||
| /** | ||
| * @param string[] $explicitlyMappedPaths TraitProxyAttributeDriver で明示登録している Entity ディレクトリ | ||
| */ | ||
| public function __construct(private array $explicitlyMappedPaths) | ||
| { | ||
| } | ||
|
|
||
| public function process(ContainerBuilder $container): void | ||
| { | ||
| $explicitlyMappedPaths = []; | ||
| foreach ($this->explicitlyMappedPaths as $path) { | ||
| $resolved = $this->resolvePath($container, $path); | ||
| if (null !== $resolved) { | ||
| $explicitlyMappedPaths[] = $resolved; | ||
| } | ||
| } | ||
|
|
||
| if ([] === $explicitlyMappedPaths) { | ||
| return; | ||
| } | ||
|
|
||
| foreach ($container->getDefinitions() as $id => $definition) { | ||
| if (!$this->isAutoMappedAttributeDriver($container, $id, $definition)) { | ||
| continue; | ||
| } | ||
|
|
||
| $arguments = $definition->getArguments(); | ||
| $paths = $arguments[0] ?? null; | ||
| if (!\is_array($paths)) { | ||
| continue; | ||
| } | ||
|
|
||
| $remaining = array_values(array_filter( | ||
| $paths, | ||
| fn ($path) => !\in_array($this->resolvePath($container, $path), $explicitlyMappedPaths, true) | ||
| )); | ||
|
|
||
| if (\count($remaining) === \count($paths)) { | ||
| continue; | ||
| } | ||
|
|
||
| if ([] === $remaining) { | ||
| // 担当パスがすべて明示登録済みになった素のドライバはチェーンから外す. | ||
| // paths が空のまま getAllClassNames() を呼ばれると例外になるため. | ||
| $this->removeFromDriverChains($container, $id); | ||
| } | ||
|
|
||
| $arguments[0] = $remaining; | ||
| $definition->setArguments($arguments); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * doctrine.orm.auto_mapping が生成する素の AttributeDriver か判定する. | ||
| */ | ||
| private function isAutoMappedAttributeDriver(ContainerBuilder $container, string $id, Definition $definition): bool | ||
| { | ||
| if (!str_starts_with($id, 'doctrine.orm.') | ||
| || !(str_ends_with($id, '_attribute_metadata_driver') | ||
| || str_ends_with($id, '_attribute_metadata_driver.inner')) | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| $class = $definition->getClass(); | ||
| if (!\is_string($class)) { | ||
| return false; | ||
| } | ||
|
|
||
| $class = $container->getParameterBag()->resolveValue($class); | ||
|
|
||
| return \is_string($class) && is_a($class, AttributeDriver::class, true); | ||
| } | ||
|
|
||
| /** | ||
| * 指定したドライバサービスへの addDriver() 呼び出しを MappingDriverChain から取り除く. | ||
| */ | ||
| private function removeFromDriverChains(ContainerBuilder $container, string $driverId): void | ||
| { | ||
| foreach ($container->getDefinitions() as $definition) { | ||
| $methodCalls = $definition->getMethodCalls(); | ||
| $remaining = array_values(array_filter( | ||
| $methodCalls, | ||
| function (array $call) use ($driverId) { | ||
| if ('addDriver' !== $call[0]) { | ||
| return true; | ||
| } | ||
|
|
||
| $driver = $call[1][0] ?? null; | ||
|
|
||
| return !($driver instanceof Reference && $driverId === (string) $driver); | ||
| } | ||
| )); | ||
|
|
||
| if (\count($remaining) !== \count($methodCalls)) { | ||
| $definition->setMethodCalls($remaining); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * パスをコンテナパラメータ解決 + realpath で正規化する. | ||
| */ | ||
| private function resolvePath(ContainerBuilder $container, mixed $path): ?string | ||
| { | ||
| if (!\is_string($path)) { | ||
| return null; | ||
| } | ||
|
|
||
| $resolved = $container->getParameterBag()->resolveValue($path); | ||
| if (!\is_string($resolved)) { | ||
| return null; | ||
| } | ||
|
|
||
| return realpath($resolved) ?: null; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.