Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,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` |

14 changes: 14 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ parametersSchema:
forbidEnumColumn: bool()
forbidRawSql: bool()
forbidMultipleTableCreations: bool()
forbidDown: bool()
])
laravel: structure([
enforceCollation: bool()
forbidAfter: bool()
forbidEnumColumn: bool()
forbidRawSql: bool()
forbidMultipleTableCreations: bool()
forbidDown: bool()
])
])

Expand All @@ -26,12 +28,14 @@ parameters:
forbidEnumColumn: false
forbidRawSql: false
forbidMultipleTableCreations: true
forbidDown: false
laravel:
enforceCollation: true
forbidAfter: true
forbidEnumColumn: false
forbidRawSql: false
forbidMultipleTableCreations: true
forbidDown: false

conditionalTags:
PhpStanMigrationRules\Rules\Phinx\EnforceCollationRule:
Expand All @@ -44,6 +48,8 @@ conditionalTags:
phpstan.rules.rule: %migrationRules.phinx.forbidRawSql%
PhpStanMigrationRules\Rules\Phinx\ForbidMultipleTableCreationsRule:
phpstan.rules.rule: %migrationRules.phinx.forbidMultipleTableCreations%
PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule:
phpstan.rules.rule: %migrationRules.phinx.forbidDown%
PhpStanMigrationRules\Rules\Laravel\EnforceCollationRule:
phpstan.rules.rule: %migrationRules.laravel.enforceCollation%
PhpStanMigrationRules\Rules\Laravel\ForbidAfterRule:
Expand All @@ -54,6 +60,8 @@ conditionalTags:
phpstan.rules.rule: %migrationRules.laravel.forbidRawSql%
PhpStanMigrationRules\Rules\Laravel\ForbidMultipleTableCreationsRule:
phpstan.rules.rule: %migrationRules.laravel.forbidMultipleTableCreations%
PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule:
phpstan.rules.rule: %migrationRules.laravel.forbidDown%

services:
-
Expand All @@ -73,6 +81,9 @@ services:
-
class: PhpStanMigrationRules\Rules\Phinx\ForbidMultipleTableCreationsRule

-
class: PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule

-
class: PhpStanMigrationRules\Rules\Laravel\EnforceCollationRule
arguments:
Expand All @@ -89,3 +100,6 @@ services:

-
class: PhpStanMigrationRules\Rules\Laravel\ForbidMultipleTableCreationsRule

-
class: PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule
49 changes: 49 additions & 0 deletions src/Rules/Laravel/NoDownMethodRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Rules\Laravel;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @extends LaravelRule<ClassMethod>
*/
final class NoDownMethodRule extends LaravelRule
{
private const string RULE_IDENTIFIER = 'laravel.schema.noDownMethod';

private const string MESSAGE =
'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.';

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)
->build(),
];
}
}
49 changes: 49 additions & 0 deletions src/Rules/Phinx/NoDownMethodRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Rules\Phinx;

use PhpParser\Node;
use PhpParser\Node\Stmt\ClassMethod;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\RuleErrorBuilder;

/**
* @extends PhinxRule<ClassMethod>
*/
final class NoDownMethodRule extends PhinxRule
{
private const string RULE_IDENTIFIER = 'phinx.schema.noDownMethod';

private const string MESSAGE =
'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.';

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)
->build(),
];
}
}
54 changes: 54 additions & 0 deletions tests/Rules/Laravel/NoDownMethodRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Laravel;

use PhpStanMigrationRules\Rules\Laravel\NoDownMethodRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<NoDownMethodRule>
*/
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. 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.',
11,
],
]
);
}

public function testReportsDownMethodInAnonymousClass(): void
{
$this->analyse(
[__DIR__ . '/fixtures/NoDownMethodAnonymous.php'],
[
[
'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.',
11,
],
]
);
}

public function testDoesNotReportChangeMethod(): void
{
$this->analyse(
[__DIR__ . '/fixtures/WithChangeMethod.php'],
[]
);
}
}
15 changes: 15 additions & 0 deletions tests/Rules/Laravel/fixtures/NoDownMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;

use Illuminate\Database\Migrations\Migration;

class NoDownMethod extends Migration
{
public function down(): void
{
// ...
}
}
15 changes: 15 additions & 0 deletions tests/Rules/Laravel/fixtures/NoDownMethodAnonymous.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;

use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
public function down(): void
{
// ...
}
};
15 changes: 15 additions & 0 deletions tests/Rules/Laravel/fixtures/WithChangeMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Laravel\fixtures;

use Illuminate\Database\Migrations\Migration;

class WithChangeMethod extends Migration
{
public function change(): void
{
// ...
}
}
41 changes: 41 additions & 0 deletions tests/Rules/Phinx/NoDownMethodRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Phinx;

use PhpStanMigrationRules\Rules\Phinx\NoDownMethodRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;

/**
* @extends RuleTestCase<NoDownMethodRule>
*/
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. 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.',
11,
],
]
);
}

public function testDoesNotReportChangeMethod(): void
{
$this->analyse(
[__DIR__ . '/fixtures/WithChangeMethod.php'],
[]
);
}
}
15 changes: 15 additions & 0 deletions tests/Rules/Phinx/fixtures/NoDownMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Phinx\fixtures;

use Phinx\Migration\AbstractMigration;

class NoDownMethod extends AbstractMigration
{
public function down(): void
{
// ...
}
}
15 changes: 15 additions & 0 deletions tests/Rules/Phinx/fixtures/WithChangeMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace PhpStanMigrationRules\Tests\Rules\Phinx\fixtures;

use Phinx\Migration\AbstractMigration;

class WithChangeMethod extends AbstractMigration
{
public function change(): void
{
// ...
}
}