From 57511dfee9439fa0599219188c66f8538951722f Mon Sep 17 00:00:00 2001 From: mscherer Date: Tue, 12 May 2026 16:33:36 +0200 Subject: [PATCH] Support sub-directory entities and tables in OrmResolver OrmResolver::getEntityPolicy() and getRepositoryPolicy() already mapped the namespace segment of the resource class from backslashes to forward slashes before handing it to App::className(), but the trailing name segment was left untouched. For a class like TestApp\Model\Entity\SubDir\Widget the resolver therefore handed App::className() the string `App.SubDir\Widget`, which short-circuits on the embedded backslash and returns null - producing a MissingPolicyException even when the policy exists at App\Policy\SubDir\WidgetPolicy. Apply the same str_replace on the name segment in both entity and repository resolution. Cover with regression tests using new SubDir Widget / WidgetsTable fixtures. --- src/Policy/OrmResolver.php | 4 ++-- tests/TestCase/Policy/OrmResolverTest.php | 22 +++++++++++++++++++ .../TestApp/Model/Entity/SubDir/Widget.php | 10 +++++++++ .../Model/Table/SubDir/WidgetsTable.php | 10 +++++++++ .../TestApp/Policy/SubDir/WidgetPolicy.php | 8 +++++++ .../Policy/SubDir/WidgetsTablePolicy.php | 8 +++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/test_app/TestApp/Model/Entity/SubDir/Widget.php create mode 100644 tests/test_app/TestApp/Model/Table/SubDir/WidgetsTable.php create mode 100644 tests/test_app/TestApp/Policy/SubDir/WidgetPolicy.php create mode 100644 tests/test_app/TestApp/Policy/SubDir/WidgetsTablePolicy.php diff --git a/src/Policy/OrmResolver.php b/src/Policy/OrmResolver.php index d52aedd1..9438aba2 100644 --- a/src/Policy/OrmResolver.php +++ b/src/Policy/OrmResolver.php @@ -103,7 +103,7 @@ protected function getEntityPolicy(EntityInterface $entity): mixed $class = $entity::class; $entityNamespace = '\Model\Entity\\'; $namespace = str_replace('\\', '/', substr($class, 0, (int)strpos($class, $entityNamespace))); - $name = substr($class, strpos($class, $entityNamespace) + strlen($entityNamespace)); + $name = str_replace('\\', '/', substr($class, strpos($class, $entityNamespace) + strlen($entityNamespace))); return $this->findPolicy($class, $name, $namespace); } @@ -119,7 +119,7 @@ protected function getRepositoryPolicy(RepositoryInterface $table): mixed $class = $table::class; $tableNamespace = '\Model\Table\\'; $namespace = str_replace('\\', '/', substr($class, 0, (int)strpos($class, $tableNamespace))); - $name = substr($class, strpos($class, $tableNamespace) + strlen($tableNamespace)); + $name = str_replace('\\', '/', substr($class, strpos($class, $tableNamespace) + strlen($tableNamespace))); return $this->findPolicy($class, $name, $namespace); } diff --git a/tests/TestCase/Policy/OrmResolverTest.php b/tests/TestCase/Policy/OrmResolverTest.php index b8d19c7e..4e1dd0cb 100644 --- a/tests/TestCase/Policy/OrmResolverTest.php +++ b/tests/TestCase/Policy/OrmResolverTest.php @@ -28,8 +28,12 @@ use OverridePlugin\Policy\TagPolicy as OverrideTagPolicy; use stdClass; use TestApp\Model\Entity\Article; +use TestApp\Model\Entity\SubDir\Widget; +use TestApp\Model\Table\SubDir\WidgetsTable; use TestApp\Policy\ArticlePolicy; use TestApp\Policy\ArticlesTablePolicy; +use TestApp\Policy\SubDir\WidgetPolicy; +use TestApp\Policy\SubDir\WidgetsTablePolicy; use TestApp\Policy\TestPlugin\BookmarkPolicy; use TestApp\Service\TestService; use TestPlugin\Model\Entity\Bookmark; @@ -68,6 +72,14 @@ public function testGetPolicyDefinedEntity(): void $this->assertInstanceOf(ArticlePolicy::class, $policy); } + public function testGetPolicyDefinedSubDirEntity(): void + { + $widget = new Widget(); + $resolver = new OrmResolver('TestApp'); + $policy = $resolver->getPolicy($widget); + $this->assertInstanceOf(WidgetPolicy::class, $policy); + } + public function testGetPolicyDefinedPluginEntityAppOveride(): void { $bookmark = new Bookmark(); @@ -108,6 +120,16 @@ public function testGetPolicyDefinedTable(): void $this->assertInstanceOf(ArticlesTablePolicy::class, $policy); } + public function testGetPolicyDefinedSubDirTable(): void + { + $widgets = $this->fetchTable('SubDir/Widgets', [ + 'className' => WidgetsTable::class, + ]); + $resolver = new OrmResolver('TestApp'); + $policy = $resolver->getPolicy($widgets); + $this->assertInstanceOf(WidgetsTablePolicy::class, $policy); + } + public function testGetPolicyQueryForDefinedTable(): void { $articles = $this->fetchTable('Articles'); diff --git a/tests/test_app/TestApp/Model/Entity/SubDir/Widget.php b/tests/test_app/TestApp/Model/Entity/SubDir/Widget.php new file mode 100644 index 00000000..2973539e --- /dev/null +++ b/tests/test_app/TestApp/Model/Entity/SubDir/Widget.php @@ -0,0 +1,10 @@ +