From ea121f54d7580b1da3c3b1653be0d9cfac767c11 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 22:54:03 +0000 Subject: [PATCH 1/3] Add Rule::namespace() shortcut method Add a convenience method that simplifies the common pattern of selecting classes by namespace. This allows: Rule::namespace('Acme') instead of: Rule::allClasses()->that(new ResideInOneOfTheseNamespaces('Acme')) Supports multiple namespaces via variadic parameters. Related to #491 --- src/Rules/Rule.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Rules/Rule.php b/src/Rules/Rule.php index 6562da7e..506db60e 100644 --- a/src/Rules/Rule.php +++ b/src/Rules/Rule.php @@ -3,10 +3,18 @@ namespace Arkitect\Rules; +use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces; +use Arkitect\Rules\DSL\AndThatShouldParser; + class Rule { public static function allClasses(): AllClasses { return new AllClasses(); } + + public static function namespace(string ...$namespaces): AndThatShouldParser + { + return self::allClasses()->that(new ResideInOneOfTheseNamespaces(...$namespaces)); + } } From 3982bc4f7288b36e6001b9fd83a28fdf01d3639a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 23 Nov 2025 22:56:46 +0000 Subject: [PATCH 2/3] Add tests for Rule::namespace() shortcut method Tests verify that the shortcut: - Works correctly to filter classes by namespace - Detects violations properly - Supports multiple namespaces - Produces equivalent results to the full syntax --- .../Integration/RuleNamespaceShortcutTest.php | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/Integration/RuleNamespaceShortcutTest.php diff --git a/tests/Integration/RuleNamespaceShortcutTest.php b/tests/Integration/RuleNamespaceShortcutTest.php new file mode 100644 index 00000000..44779925 --- /dev/null +++ b/tests/Integration/RuleNamespaceShortcutTest.php @@ -0,0 +1,148 @@ +createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Happy*')) + ->because("that's what she said"); + + $runner->run($dir, $rule); + + self::assertCount(0, $runner->getViolations()); + self::assertCount(0, $runner->getParsingErrors()); + } + + public function test_namespace_shortcut_detects_violations(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Sad*')) + ->because('we want sad names'); + + $runner->run($dir, $rule); + + self::assertCount(1, $runner->getViolations()); + } + + public function test_namespace_shortcut_supports_multiple_namespaces(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + $rule = Rule::namespace('App\HappyIsland', 'App\BadCode') + ->should(new HaveNameMatching('*Code')) + ->because('we want Code suffix'); + + $runner->run($dir, $rule); + + // HappyClass doesn't match *Code pattern, so we should have 1 violation + self::assertCount(1, $runner->getViolations()); + } + + public function test_namespace_shortcut_is_equivalent_to_full_syntax(): void + { + $dir = vfsStream::setup('root', null, $this->createDummyProject())->url(); + + $runner = TestRunner::create('8.4'); + + // Using shortcut + $shortcutRule = Rule::namespace('App\HappyIsland') + ->should(new HaveNameMatching('Happy*')) + ->because('test'); + + $runner->run($dir, $shortcutRule); + $shortcutViolations = $runner->getViolations(); + + // Using full syntax + $fullRule = Rule::allClasses() + ->that(new ResideInOneOfTheseNamespaces('App\HappyIsland')) + ->should(new HaveNameMatching('Happy*')) + ->because('test'); + + $runner->run($dir, $fullRule); + $fullViolations = $runner->getViolations(); + + self::assertCount(\count($shortcutViolations), $fullViolations); + } + + public function createDummyProject(): array + { + return [ + 'BadCode' => [ + 'BadCode.php' => <<<'EOF' + happy = $happy; + } + } + EOF, + ], + 'OtherBadCode' => [ + 'OtherBadCode.php' => <<<'EOF' + happy = $happy; + } + } + EOF, + ], + + 'HappyIsland' => [ + 'HappyClass.php' => <<<'EOF' + bad = $bad; + } + } + EOF, + ], + ]; + } +} From e03e7ba722266299039a8c98d50e3582023c9184 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Nov 2025 14:59:26 +0000 Subject: [PATCH 3/3] Document Rule::namespace() shortcut in README Add example showing the shortcut syntax in the introduction section, right after the standard rule example. --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 882dc41a..75ee5b62 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,17 @@ Rule::allClasses() ->should(new HaveNameMatching('*Controller')) ->because('it\'s a symfony naming convention'); ``` + +Since selecting classes by namespace is very common, there's a convenient shortcut: + +```php +Rule::namespace('App\Controller') + ->should(new HaveNameMatching('*Controller')) + ->because('it\'s a symfony naming convention'); +``` + +You can also specify multiple namespaces: `Rule::namespace('App\Controller', 'App\Service')`. + # Installation ## Using Composer