Skip to content

Commit 5a95852

Browse files
Implement NoDownMethodRule for Laravel and Phinx (#1)
1 parent 360d68d commit 5a95852

11 files changed

Lines changed: 296 additions & 0 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,17 @@ Ensures each migration creates at most one table.
108108
|---|---|
109109
| [Phinx](./src/Rules/Phinx/ForbidMultipleTableCreationsRule.php) | Multiple calls to `create()` on table instances |
110110
| [Laravel](./src/Rules/Laravel/ForbidMultipleTableCreationsRule.php) | Multiple `Schema::create()` calls in the same migration |
111+
112+
---
113+
114+
### Rule: `NoDownMethodRule`
115+
Forbids the usage of the `down` method in migrations.
116+
> Useful for teams that prefer forward-only migrations or rely solely on the `change` method for extensive rollback support where possible.
117+
118+
#### Support
119+
120+
| Framework | Forbidden usage |
121+
|---|---|
122+
| [Phinx](./src/Rules/Phinx/NoDownMethodRule.php) | `public function down(): void` |
123+
| [Laravel](./src/Rules/Laravel/NoDownMethodRule.php) | `public function down(): void` |
124+

extension.neon

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ parametersSchema:
77
forbidEnumColumn: bool()
88
forbidRawSql: bool()
99
forbidMultipleTableCreations: bool()
10+
forbidDown: bool()
1011
])
1112
laravel: structure([
1213
enforceCollation: bool()
1314
forbidAfter: bool()
1415
forbidEnumColumn: bool()
1516
forbidRawSql: bool()
1617
forbidMultipleTableCreations: bool()
18+
forbidDown: bool()
1719
])
1820
])
1921

@@ -26,12 +28,14 @@ parameters:
2628
forbidEnumColumn: false
2729
forbidRawSql: false
2830
forbidMultipleTableCreations: true
31+
forbidDown: false
2932
laravel:
3033
enforceCollation: true
3134
forbidAfter: true
3235
forbidEnumColumn: false
3336
forbidRawSql: false
3437
forbidMultipleTableCreations: true
38+
forbidDown: false
3539

3640
conditionalTags:
3741
PhpStanMigrationRules\Rules\Phinx\EnforceCollationRule:
@@ -44,6 +48,8 @@ conditionalTags:
4448
phpstan.rules.rule: %migrationRules.phinx.forbidRawSql%
4549
PhpStanMigrationRules\Rules\Phinx\ForbidMultipleTableCreationsRule:
4650
phpstan.rules.rule: %migrationRules.phinx.forbidMultipleTableCreations%
51+
PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule:
52+
phpstan.rules.rule: %migrationRules.phinx.forbidDown%
4753
PhpStanMigrationRules\Rules\Laravel\EnforceCollationRule:
4854
phpstan.rules.rule: %migrationRules.laravel.enforceCollation%
4955
PhpStanMigrationRules\Rules\Laravel\ForbidAfterRule:
@@ -54,6 +60,8 @@ conditionalTags:
5460
phpstan.rules.rule: %migrationRules.laravel.forbidRawSql%
5561
PhpStanMigrationRules\Rules\Laravel\ForbidMultipleTableCreationsRule:
5662
phpstan.rules.rule: %migrationRules.laravel.forbidMultipleTableCreations%
63+
PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule:
64+
phpstan.rules.rule: %migrationRules.laravel.forbidDown%
5765

5866
services:
5967
-
@@ -73,6 +81,9 @@ services:
7381
-
7482
class: PhpStanMigrationRules\Rules\Phinx\ForbidMultipleTableCreationsRule
7583

84+
-
85+
class: PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule
86+
7687
-
7788
class: PhpStanMigrationRules\Rules\Laravel\EnforceCollationRule
7889
arguments:
@@ -89,3 +100,6 @@ services:
89100

90101
-
91102
class: PhpStanMigrationRules\Rules\Laravel\ForbidMultipleTableCreationsRule
103+
104+
-
105+
class: PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Rules\Laravel;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\ClassMethod;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @extends LaravelRule<ClassMethod>
14+
*/
15+
final class NoDownMethodRule extends LaravelRule
16+
{
17+
private const string RULE_IDENTIFIER = 'laravel.schema.noDownMethod';
18+
19+
private const string MESSAGE =
20+
'Forbidden: "down" method. '
21+
. 'Why: a "down" method enables rollbacks, which can cause data loss and break forward-only migration strategies. '
22+
. 'Fix: use the "change" method for reversible migrations, or omit the rollback path entirely.';
23+
24+
public function getNodeType(): string
25+
{
26+
return ClassMethod::class;
27+
}
28+
29+
public function processNode(Node $node, Scope $scope): array
30+
{
31+
if (!$this->isLaravelMigration($scope)) {
32+
return [];
33+
}
34+
35+
if ($node->name->toString() !== 'down') {
36+
return [];
37+
}
38+
39+
if (!$node->isPublic()) {
40+
return [];
41+
}
42+
43+
return [
44+
RuleErrorBuilder::message(self::MESSAGE)
45+
->identifier(self::RULE_IDENTIFIER)
46+
->build(),
47+
];
48+
}
49+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Rules\Phinx;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Stmt\ClassMethod;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\Rules\RuleErrorBuilder;
11+
12+
/**
13+
* @extends PhinxRule<ClassMethod>
14+
*/
15+
final class NoDownMethodRule extends PhinxRule
16+
{
17+
private const string RULE_IDENTIFIER = 'phinx.schema.noDownMethod';
18+
19+
private const string MESSAGE =
20+
'Forbidden: "down" method. '
21+
. 'Why: a "down" method enables rollbacks, which can cause data loss and break forward-only migration strategies. '
22+
. 'Fix: use the "change" method for reversible migrations, or omit the rollback path entirely.';
23+
24+
public function getNodeType(): string
25+
{
26+
return ClassMethod::class;
27+
}
28+
29+
public function processNode(Node $node, Scope $scope): array
30+
{
31+
if (!$this->isPhinxMigration($scope)) {
32+
return [];
33+
}
34+
35+
if ($node->name->toString() !== 'down') {
36+
return [];
37+
}
38+
39+
if (!$node->isPublic()) {
40+
return [];
41+
}
42+
43+
return [
44+
RuleErrorBuilder::message(self::MESSAGE)
45+
->identifier(self::RULE_IDENTIFIER)
46+
->build(),
47+
];
48+
}
49+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Laravel;
6+
7+
use PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<NoDownMethodRule>
13+
*/
14+
final class NoDownMethodRuleTest extends RuleTestCase
15+
{
16+
protected function getRule(): Rule
17+
{
18+
return new NoDownMethodRule();
19+
}
20+
21+
public function testReportsDownMethod(): void
22+
{
23+
$this->analyse(
24+
[__DIR__ . '/fixtures/NoDownMethod.php'],
25+
[
26+
[
27+
'Forbidden: "down" method. Why: a "down" method enables rollbacks, which can cause data loss and break forward-only migration strategies. Fix: use the "change" method for reversible migrations, or omit the rollback path entirely.',
28+
11,
29+
],
30+
]
31+
);
32+
}
33+
34+
public function testReportsDownMethodInAnonymousClass(): void
35+
{
36+
$this->analyse(
37+
[__DIR__ . '/fixtures/NoDownMethodAnonymous.php'],
38+
[
39+
[
40+
'Forbidden: "down" method. Why: a "down" method enables rollbacks, which can cause data loss and break forward-only migration strategies. Fix: use the "change" method for reversible migrations, or omit the rollback path entirely.',
41+
11,
42+
],
43+
]
44+
);
45+
}
46+
47+
public function testDoesNotReportChangeMethod(): void
48+
{
49+
$this->analyse(
50+
[__DIR__ . '/fixtures/WithChangeMethod.php'],
51+
[]
52+
);
53+
}
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;
6+
7+
use Illuminate\Database\Migrations\Migration;
8+
9+
class NoDownMethod extends Migration
10+
{
11+
public function down(): void
12+
{
13+
// ...
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;
6+
7+
use Illuminate\Database\Migrations\Migration;
8+
9+
return new class extends Migration
10+
{
11+
public function down(): void
12+
{
13+
// ...
14+
}
15+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;
6+
7+
use Illuminate\Database\Migrations\Migration;
8+
9+
class WithChangeMethod extends Migration
10+
{
11+
public function change(): void
12+
{
13+
// ...
14+
}
15+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Phinx;
6+
7+
use PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule;
8+
use PHPStan\Rules\Rule;
9+
use PHPStan\Testing\RuleTestCase;
10+
11+
/**
12+
* @extends RuleTestCase<NoDownMethodRule>
13+
*/
14+
final class NoDownMethodRuleTest extends RuleTestCase
15+
{
16+
protected function getRule(): Rule
17+
{
18+
return new NoDownMethodRule();
19+
}
20+
21+
public function testReportsDownMethod(): void
22+
{
23+
$this->analyse(
24+
[__DIR__ . '/fixtures/NoDownMethod.php'],
25+
[
26+
[
27+
'Forbidden: "down" method. Why: a "down" method enables rollbacks, which can cause data loss and break forward-only migration strategies. Fix: use the "change" method for reversible migrations, or omit the rollback path entirely.',
28+
11,
29+
],
30+
]
31+
);
32+
}
33+
34+
public function testDoesNotReportChangeMethod(): void
35+
{
36+
$this->analyse(
37+
[__DIR__ . '/fixtures/WithChangeMethod.php'],
38+
[]
39+
);
40+
}
41+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpStanMigrationRules\Tests\Rules\Phinx\fixtures;
6+
7+
use Phinx\Migration\AbstractMigration;
8+
9+
class NoDownMethod extends AbstractMigration
10+
{
11+
public function down(): void
12+
{
13+
// ...
14+
}
15+
}

0 commit comments

Comments
 (0)