From a0c5e990fe339e6d8f2d21b1606216e39ba8f981 Mon Sep 17 00:00:00 2001 From: Kentaro Ohkouchi Date: Fri, 24 Jul 2026 13:12:26 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix(doctrine):=20EccubeBundle=20=E3=81=AE?= =?UTF-8?q?=20auto=5Fmapping=20=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= =?UTF-8?q?=E3=81=97=20Proxy=20=E3=81=A8=E3=81=AE=E4=BA=8C=E9=87=8D?= =?UTF-8?q?=E5=AE=A3=E8=A8=80=E3=82=92=E9=98=B2=E6=AD=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entity の if(!class_exists()) ガード全廃 (#6895) 後、プラグイン/Customize が コア Entity を trait 拡張して Proxy (app/proxy/entity) が生成されている環境で、 prod 起動時に "Cannot redeclare class Eccube\Entity\Customer" で全リクエストが 500 になっていた。 原因は Doctrine の mapping driver が二重に登録されていること: - Kernel::addEntityExtensionPass が src/Eccube/Entity を TraitProxyAttributeDriver で登録 (Proxy 済みクラスは再 require しない) - doctrine.orm.auto_mapping が EccubeBundle を検出し、同じ src/Eccube/Entity を 素の AttributeDriver でも登録する。こちらは ColocatedMappingDriver::getAllClassNames() で Entity ソースを無条件に require_once するため、Kernel::loadEntityProxies が 先に Proxy を読み込んだ状態で二重宣言になる (旧: 各 Entity の class_exists ガードが吸収) auto_mapping 全体を切るのではなく EccubeBundle の自動マッピングのみ無効化する (他バンドルの auto_mapping は維持)。 検証 (ローカル・SQLite・APP_ENV=prod): - 修正前: samplepayment44 を enable し Proxy が生成された状態で cache:clear / ビルトインサーバへのリクエストが redeclare fatal (CI の失敗と同一スタック) - 修正後: cache:clear / warmup 成功、doctrine:mapping:info は修正前と同じ 82 entities (league/oauth2-server-bundle の 5 entity も維持)、e2e/agent の ACP discovery (7 assertions) と UCP checkout session (10 assertions) が PASS Co-Authored-By: Claude Opus 4.8 (1M context) --- app/config/eccube/packages/doctrine.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/config/eccube/packages/doctrine.yaml b/app/config/eccube/packages/doctrine.yaml index f8805bcbe44..cf9c34df96a 100644 --- a/app/config/eccube/packages/doctrine.yaml +++ b/app/config/eccube/packages/doctrine.yaml @@ -30,6 +30,12 @@ doctrine: auto_generate_proxy_classes: '%kernel.debug%' naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true + # EccubeBundle の Entity は Kernel::addEntityExtensionPass が TraitProxyAttributeDriver で + # 明示登録する。auto_mapping が同じ src/Eccube/Entity を素の AttributeDriver でも登録すると、 + # そちらが Entity ソースを無条件に require_once するため、Proxy (app/proxy/entity) を + # ロード済みの状態で "Cannot redeclare class" となる (Entity の class_exists ガード全廃後). + mappings: + EccubeBundle: false controller_resolver: auto_mapping: false dql: From 0fb4a6c20a06c678691d339781d0f75657e0c0a8 Mon Sep 17 00:00:00 2001 From: Kentaro Ohkouchi Date: Fri, 24 Jul 2026 13:21:42 +0900 Subject: [PATCH 2/3] =?UTF-8?q?test(doctrine):=20=E3=82=B3=E3=82=A2=20Enti?= =?UTF-8?q?ty=20=E3=81=AE=E3=83=9E=E3=83=83=E3=83=94=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=81=8C=20TraitProxyAttributeDriver=20=E9=99=90=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=82=E3=82=8B=E3=81=93=E3=81=A8=E3=82=92=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/Eccube/Entity を担当するドライバが TraitProxyAttributeDriver 以外に存在しないことを 検証する。素の AttributeDriver が同じパスを担当すると、Entity ソースを無条件に require_once するため Proxy ロード済み環境で redeclare fatal になる。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../EccubeEntityMetadataDriverTest.php | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php diff --git a/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php b/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php new file mode 100644 index 00000000000..0f034d2e930 --- /dev/null +++ b/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php @@ -0,0 +1,93 @@ +get(EntityManagerInterface::class); + $driver = $entityManager->getConfiguration()->getMetadataDriverImpl(); + + $coreEntityDir = realpath(__DIR__.'/../../../../../../src/Eccube/Entity'); + $this->assertIsString($coreEntityDir); + + foreach ($this->flattenDrivers($driver) as $each) { + if (!method_exists($each, 'getPaths')) { + continue; + } + foreach ($each->getPaths() as $path) { + if (realpath($path) !== $coreEntityDir) { + continue; + } + $this->assertInstanceOf(TraitProxyAttributeDriver::class, $each, 'src/Eccube/Entity は TraitProxyAttributeDriver 以外から登録してはならない' + .' (素の AttributeDriver は Entity ソースを無条件に require_once するため、' + .'Proxy ロード済みの環境で "Cannot redeclare class" になる)'); + } + } + } + + /** + * MappingDriverChain を再帰的に展開して、実際にマッピングを解決するドライバを列挙する. + * + * @return list + */ + private function flattenDrivers(?MappingDriver $driver): array + { + if ($driver === null) { + return []; + } + + // doctrine-bundle の MappingDriver は実ドライバをラップしているため中身を取り出す + if ($driver instanceof BundleMappingDriver) { + return $this->flattenDrivers($driver->getDriver()); + } + + if (!$driver instanceof MappingDriverChain) { + return [$driver]; + } + + $drivers = []; + foreach ($driver->getDrivers() as $each) { + $drivers = [...$drivers, ...$this->flattenDrivers($each)]; + } + + return [...$drivers, ...$this->flattenDrivers($driver->getDefaultDriver())]; + } +} From 1b3396952ee8f66cc60ada2bc890b054672885a3 Mon Sep 17 00:00:00 2001 From: Kentaro Ohkouchi Date: Fri, 24 Jul 2026 13:31:52 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test(doctrine):=20=E3=82=B3=E3=82=A2=20Enti?= =?UTF-8?q?ty=20=E3=81=AE=E3=83=9E=E3=83=83=E3=83=94=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=83=89=E3=83=A9=E3=82=A4=E3=83=90=E3=81=8C=200=20=E4=BB=B6?= =?UTF-8?q?=E3=81=A7=E3=81=AA=E3=81=84=E3=81=93=E3=81=A8=E3=82=82=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 対象パスのドライバが 1 件も無い構成 (Kernel::addEntityExtensionPass の明示登録が 失われた場合) でもループが素通りして green になるため、収集した対象ドライバが 空でないことをアサートする。 CodeRabbit のレビュー指摘に対応。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../ORM/Mapping/EccubeEntityMetadataDriverTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php b/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php index 0f034d2e930..046f30e9a6b 100644 --- a/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php +++ b/tests/Eccube/Tests/Doctrine/ORM/Mapping/EccubeEntityMetadataDriverTest.php @@ -48,6 +48,7 @@ public function testCoreEntityPathIsMappedOnlyByTraitProxyAttributeDriver(): voi $coreEntityDir = realpath(__DIR__.'/../../../../../../src/Eccube/Entity'); $this->assertIsString($coreEntityDir); + $coreEntityDrivers = []; foreach ($this->flattenDrivers($driver) as $each) { if (!method_exists($each, 'getPaths')) { continue; @@ -56,11 +57,18 @@ public function testCoreEntityPathIsMappedOnlyByTraitProxyAttributeDriver(): voi if (realpath($path) !== $coreEntityDir) { continue; } + $coreEntityDrivers[] = $each; $this->assertInstanceOf(TraitProxyAttributeDriver::class, $each, 'src/Eccube/Entity は TraitProxyAttributeDriver 以外から登録してはならない' .' (素の AttributeDriver は Entity ソースを無条件に require_once するため、' .'Proxy ロード済みの環境で "Cannot redeclare class" になる)'); } } + + // 対象ドライバが 0 件でもループが素通りするため、明示登録そのものが失われた構成も検知する + $this->assertNotEmpty( + $coreEntityDrivers, + 'src/Eccube/Entity のマッピングドライバ (Kernel::addEntityExtensionPass の TraitProxyAttributeDriver) が登録されていない' + ); } /**