diff --git a/README.md b/README.md index 71a80d5..c45c337 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,17 @@ Ensures each migration creates at most one table. |---|---| | [Phinx](./src/Rules/Phinx/ForbidMultipleTableCreationsRule.php) | Multiple calls to `create()` on table instances | | [Laravel](./src/Rules/Laravel/ForbidMultipleTableCreationsRule.php) | Multiple `Schema::create()` calls in the same migration | + +--- + +### Rule: `NoDownMethodRule` +Forbids the usage of the `down` method in migrations. +> Useful for teams that prefer forward-only migrations or rely solely on the `change` method for extensive rollback support where possible. + +#### Support + +| Framework | Forbidden usage | +|---|---| +| [Phinx](./src/Rules/Phinx/NoDownMethodRule.php) | `public function down(): void` | +| [Laravel](./src/Rules/Laravel/NoDownMethodRule.php) | `public function down(): void` | + diff --git a/extension.neon b/extension.neon index e019b69..a37ad3a 100644 --- a/extension.neon +++ b/extension.neon @@ -39,5 +39,15 @@ services: - class: PhpStanMigrationRules\Rules\Laravel\ForbidMultipleTableCreationsRule + tags: + - phpstan.rules.rule + + - + class: PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule + tags: + - phpstan.rules.rule + + - + class: PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule tags: - phpstan.rules.rule \ No newline at end of file diff --git a/src/Rules/Laravel/NoDownMethodRule.php b/src/Rules/Laravel/NoDownMethodRule.php new file mode 100644 index 0000000..a9fc902 --- /dev/null +++ b/src/Rules/Laravel/NoDownMethodRule.php @@ -0,0 +1,47 @@ + + */ +final class NoDownMethodRule extends LaravelRule +{ + private const string RULE_IDENTIFIER = 'laravel.schema.noDownMethod'; + + private const string MESSAGE = 'Forbidden: "down" method. Use "change" method for reversible migrations, or forward-only migrations.'; + + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->isLaravelMigration($scope)) { + return []; + } + + if ($node->name->toString() !== 'down') { + return []; + } + + if (!$node->isPublic()) { + return []; + } + + return [ + RuleErrorBuilder::message(self::MESSAGE) + ->identifier(self::RULE_IDENTIFIER) + ->tip('If you must rollback, consider creating a new migration that reverses these changes.') + ->build(), + ]; + } +} diff --git a/src/Rules/Phinx/NoDownMethodRule.php b/src/Rules/Phinx/NoDownMethodRule.php new file mode 100644 index 0000000..375a0fa --- /dev/null +++ b/src/Rules/Phinx/NoDownMethodRule.php @@ -0,0 +1,47 @@ + + */ +final class NoDownMethodRule extends PhinxRule +{ + private const string RULE_IDENTIFIER = 'phinx.schema.noDownMethod'; + + private const string MESSAGE = 'Forbidden: "down" method. Use "change" method for reversible migrations, or forward-only migrations.'; + + public function getNodeType(): string + { + return ClassMethod::class; + } + + public function processNode(Node $node, Scope $scope): array + { + if (!$this->isPhinxMigration($scope)) { + return []; + } + + if ($node->name->toString() !== 'down') { + return []; + } + + if (!$node->isPublic()) { + return []; + } + + return [ + RuleErrorBuilder::message(self::MESSAGE) + ->identifier(self::RULE_IDENTIFIER) + ->tip('If you must rollback, consider creating a new migration that reverses these changes.') + ->build(), + ]; + } +} diff --git a/tests/Rules/Laravel/NoDownMethodRuleTest.php b/tests/Rules/Laravel/NoDownMethodRuleTest.php new file mode 100644 index 0000000..5a346dc --- /dev/null +++ b/tests/Rules/Laravel/NoDownMethodRuleTest.php @@ -0,0 +1,56 @@ + + */ +final class NoDownMethodRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new NoDownMethodRule(); + } + + public function testReportsDownMethod(): void + { + $this->analyse( + [__DIR__ . '/fixtures/NoDownMethod.php'], + [ + [ + 'Forbidden: "down" method. Use "change" method for reversible migrations, or forward-only migrations.', + 11, + 'If you must rollback, consider creating a new migration that reverses these changes.', + ], + ] + ); + } + + public function testReportsDownMethodInAnonymousClass(): void + { + $this->analyse( + [__DIR__ . '/fixtures/NoDownMethodAnonymous.php'], + [ + [ + 'Forbidden: "down" method. Use "change" method for reversible migrations, or forward-only migrations.', + 11, + 'If you must rollback, consider creating a new migration that reverses these changes.', + ], + ] + ); + } + + public function testDoesNotReportChangeMethod(): void + { + $this->analyse( + [__DIR__ . '/fixtures/WithChangeMethod.php'], + [] + ); + } +} diff --git a/tests/Rules/Laravel/fixtures/NoDownMethod.php b/tests/Rules/Laravel/fixtures/NoDownMethod.php new file mode 100644 index 0000000..a74c226 --- /dev/null +++ b/tests/Rules/Laravel/fixtures/NoDownMethod.php @@ -0,0 +1,15 @@ + + */ +final class NoDownMethodRuleTest extends RuleTestCase +{ + protected function getRule(): Rule + { + return new NoDownMethodRule(); + } + + public function testReportsDownMethod(): void + { + $this->analyse( + [__DIR__ . '/fixtures/NoDownMethod.php'], + [ + [ + 'Forbidden: "down" method. Use "change" method for reversible migrations, or forward-only migrations.', + 11, + 'If you must rollback, consider creating a new migration that reverses these changes.', + ], + ] + ); + } + + public function testDoesNotReportChangeMethod(): void + { + $this->analyse( + [__DIR__ . '/fixtures/WithChangeMethod.php'], + [] + ); + } +} diff --git a/tests/Rules/Phinx/fixtures/NoDownMethod.php b/tests/Rules/Phinx/fixtures/NoDownMethod.php new file mode 100644 index 0000000..d1f2fe2 --- /dev/null +++ b/tests/Rules/Phinx/fixtures/NoDownMethod.php @@ -0,0 +1,15 @@ +