Search by

jcergolj / rector-for-laravel

jcergolj

Rector rules for Laravel projects

Package info

github.com/jcergolj/rector-for-laravel

pkg:composer/jcergolj/rector-for-laravel

Statistics

Installs: 681

Dependents: 1

Suggesters: 0

Stars: 1

Open Issues: 1

v12 2026-11-03 12:50 UTC

This package is auto-updated.

Last update: 2026-11-03 12:50:54 UTC


README

A reusable Rector package for Laravel projects, containing custom rules and default configuration to enforce consistent refactoring patterns across multiple projects.

Installation

Install the package in your Laravel project:

composer require jcergolj/rector-for-laravel --dev

This package also depends on:

These will be pulled in automatically if not already installed.

Requires PHP 8.3 or newer through driftingly/rector-laravel 2.7. Running this package's PHPUnit development suite requires PHP 8.4.1 or newer. Dependencies use stable releases by default.

driftingly/rector-laravel 2.7.3 requires the abandoned symplify/rule-doc-generator-contracts package, so Composer may print an abandonment warning. It does not prevent installation. There is no suggested replacement; removing it requires a change in the upstream Laravel Rector dependency, not simply deleting it from this package's dependencies.

⚙️ Usage Options

You can use the default rector.php config file in two ways:

✅ Option 1: Publish the Config to Project Root (Laravel Style)

You can publish the default rector.php file to the root of your Laravel project using:

php artisan vendor:publish --tag=rector-for-laravel-config

This will copy rector.php from the package into the root of your Laravel project:

./rector.php

You can then modify this file to suit your project-specific rules and paths.

🟡 Option 2: Use the Config Directly from the Package

You can run Rector with the config file located in the package itself:

vendor/bin/rector process --config=vendor/jcergolj/rector-for-laravel/config/rector.php

This is a good option if you want to keep the default setup with no local modifications.

Included Custom Rules

If you use this package's default rector.php config, all five custom rules below are enabled.

AddBlankLineAfterPhpUnitAssertionRector

Adds a blank line around assertions inside PHPUnit test methods so assertion blocks are easier to scan.

public function test_user_can_log_in(): void
{
    $user = User::factory()->create();
    $this->assertTrue(true);
    $this->assertSame('John', $user->name);
}

becomes:

public function test_user_can_log_in(): void
{
    $user = User::factory()->create();

    $this->assertTrue(true);

    $this->assertSame('John', $user->name);
}

This rule only targets PHPUnit test methods.

ArrowFunctionToClosureRector

Rewrites PHP arrow functions to equivalent closures with explicit use (...) captures.

$names = array_map(fn (string $name): string => $prefix.$name, $users);

becomes:

$names = array_map(function (string $name) use ($prefix): string {
    return $prefix.$name;
}, $users);

Preserves types, captures and function signatures, including nested arrows.

ModelCastsPropertyToCastsMethodRector

Converts Eloquent protected $casts = [...] properties into protected function casts(): array methods.

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $casts = [
        'age' => 'integer',
    ];
}

becomes:

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected function casts(): array
    {
        return [
            'age' => 'integer',
        ];
    }
}

Enable this only on Laravel versions that support defining model casts via a casts(): array method. The rule skips classes that already define casts().

ModelQueryToDirectStaticCallRector

Rewrites fluent Eloquent chains that start with Model::query() to the shorter direct static form when the rewrite is safe.

Invoice::query()->where('status', 'paid')->latest()->first();
Invoice::query()->with('customer')->orderBy('created_at', 'desc')->get();

becomes:

Invoice::where('status', 'paid')->latest()->first();
Invoice::with('customer')->orderBy('created_at', 'desc')->get();

Skips standalone query() calls and non-Eloquent classes.

PhpUnitTestDocblockToAttributeRector

Converts PHPUnit @test docblock annotations to the native PHP 8 #[Test] attribute.

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    /**
     * @test
     */
    public function user_can_log_in(): void
    {
    }
}

becomes:

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    #[Test]
    public function user_can_log_in(): void
    {
    }
}

Preserves other annotations and avoids duplicate #[Test] attributes.

Opt-In Rules

These rules are disabled by default. Uncomment them in config/rector.php or register them as shown below.

All WrapLong* rules use a configurable soft target of 120 characters, including indentation. They leave existing multiline layouts unchanged and may still produce lines over the target. Targets must be positive integers; Unicode characters count as one and tabs as four spaces. Run Pint after Rector for project styling.

WrapLongListsRector

Wraps long inline arrays, call argument lists and declaration parameter lists with one item per line. Supports nested arrays, function/method/constructor calls, closures, arrow functions and promoted constructor parameters. Enclosing multi-argument calls are expanded when their inline arrays need wrapping.

use Jcergolj\RectorForLaravel\CustomRules\WrapLongListsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withConfiguredRule(WrapLongListsRector::class, [
        WrapLongListsRector::MAX_LINE_LENGTH => 120,
    ]);

For example:

$token = Token::factory()->for(User::factory()->create(['email' => 'joe.doe@example.com', 'first_name' => 'joe', 'last_name' => 'doe']), 'buyer');

becomes:

$token = Token::factory()->for(
    User::factory()->create([
        'email' => 'joe.doe@example.com',
        'first_name' => 'joe',
        'last_name' => 'doe',
    ]),
    'buyer',
);

Supports both [] and array(). Skips empty and single-item lists, existing multiline lists, first-class callable references and anonymous-class constructor argument lists. Never splits strings. Parameter-list length is measured through the closing parenthesis, excluding return types and bodies; parameter wrapping is skipped in files containing __LINE__.

WrapLongChainsRector

Wraps long method, property and mixed access chains before -> or ?->:

$value = $this->completedQuestionnaireWithADescriptiveName->sectionQuestionsWithADescriptiveName->first()->questionOneWithADescriptiveName;

becomes:

$value = $this->completedQuestionnaireWithADescriptiveName
    ->sectionQuestionsWithADescriptiveName
    ->first()
    ->questionOneWithADescriptiveName;
use Jcergolj\RectorForLaravel\CustomRules\WrapLongChainsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withConfiguredRule(WrapLongChainsRector::class, [
        WrapLongChainsRector::MAX_LINE_LENGTH => 120,
    ]);

Supports nested and nullsafe chains. Keeps the first access beside a variable receiver, and skips indivisible single accesses, existing multiline chains, dynamic property names and first-class callable chains.

The previous separate list and chain rules have been removed. Use the two merged rules above, with one shared MAX_LINE_LENGTH target per rule. Both remain opt-in; the five default custom rules are unchanged.

BlankLineBetweenUnrelatedAssignmentsRector

Adds a blank line between adjacent = assignments to unrelated local variables or statically named $this properties. Applies in tests and application code, including functions, methods, closures, nested blocks and file-level code. Line length and chaining do not affect the decision.

$user = User::factory()->create();
$name = $user->name;
$invoice = Invoice::factory()->create();
$total = $invoice->total;
$enabled = true;

becomes:

$user = User::factory()->create();
$name = $user->name;

$invoice = Invoice::factory()->create();
$total = $invoice->total;

$enabled = true;

Enable it explicitly:

use Jcergolj\RectorForLaravel\CustomRules\BlankLineBetweenUnrelatedAssignmentsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withRules([
        BlankLineBetweenUnrelatedAssignmentsRector::class,
    ]);

Assignments stay together when they target the same variable/property, or either right-hand side directly references the other's target. Explicit closure captures and arrow-function captures count; shadowed parameters and anonymous-class bodies do not. Sharing a common input alone does not make two assignments related. Only adjacent assignments are compared: this is not whole-program dependency or alias analysis, and hidden dependencies inside called methods are not inferred.

Preserves existing blank lines and comments. Skips pairs with a trailing comment on the first assignment, dynamic targets/reads, array-offset and destructuring targets, reference/compound assignments, and ambiguous expressions such as nested assignments, increments, compact(), extract(), get_defined_vars(), eval and include. Does not wrap lines itself; works with the wrapping rules in one run. Replaces the two previous fixture and multiline-property spacing rules, which have been removed without compatibility aliases. This rule remains opt-in.

WrapLongConcatenationsRector

Wraps long concatenations with dots at the end of continued lines:

'attribute' => trans('validation.attributes.email').
    trans('dashboard.on_the').
    trans('allocated-tokens.create.step_1').
    trans('dashboard.tab'),

Enable it explicitly in your rector.php:

use Jcergolj\RectorForLaravel\CustomRules\WrapLongConcatenationsRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withConfiguredRule(WrapLongConcatenationsRector::class, [
        WrapLongConcatenationsRector::MAX_LINE_LENGTH => 120,
    ]);

Supports nested concatenations in calls and arrays. Never splits string literals.

WrapLongBooleanConditionsRector

Wraps long if, elseif, while and do/while conditions using || or &&, with operators at the end of continued lines:

if (
    $request->routeIs('licence-agreement.create') ||
    $request->routeIs('licence-agreement.store') ||
    $request->routeIs('download-licence-agreement')
) {
    // Existing body unchanged.
}

Enable it explicitly in your rector.php:

use Jcergolj\RectorForLaravel\CustomRules\WrapLongBooleanConditionsRector;

return RectorConfig::configure()
    ->withConfiguredRule(WrapLongBooleanConditionsRector::class, [
        WrapLongBooleanConditionsRector::MAX_LINE_LENGTH => 120,
    ]);

Preserves grouping and short-circuiting. Skips and, or, xor, for headers and boolean expressions outside these conditions.

MiddlewareTestsFirstRector

Moves tests calling assertMiddlewareIsApplied() before other tests, preserving order within each group.

public function test_can_view_tokens(): void
{
    $this->get('/tokens')->assertOk();
}

public function test_auth_is_applied(): void
{
    $this->get('/tokens')->assertMiddlewareIsApplied('auth');
}

becomes:

public function test_auth_is_applied(): void
{
    $this->get('/tokens')->assertMiddlewareIsApplied('auth');
}

public function test_can_view_tokens(): void
{
    $this->get('/tokens')->assertOk();
}

Non-test members stay in place. Calls inside closures or nested classes do not count. Skips classes with trailing inline member comments. Run Pint afterward if needed.

DataProviderBeforeTestRector

Moves each local PHPUnit data provider above the first test that uses it.

public static function values(): array
{
    return [['value']];
}

#[\PHPUnit\Framework\Attributes\DataProvider('values')]
public function test_value(string $value): void
{
    $this->assertSame('value', $value);
}

Supports #[DataProvider('method')] and @dataProvider method. Skips inherited, external and unused providers, and classes with trailing inline member comments. Works with MiddlewareTestsFirstRector. Run Pint afterward if needed.

To enable any of these test rules, add them to your rector.php:

use Jcergolj\RectorForLaravel\CustomRules\DataProviderBeforeTestRector;
use Jcergolj\RectorForLaravel\CustomRules\MiddlewareTestsFirstRector;
use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withRules([
        MiddlewareTestsFirstRector::class,
        DataProviderBeforeTestRector::class,
    ]);

RemoveFillableFromUnguardedModelsRector

Removes $fillable properties from Eloquent models for projects that intentionally run with unguarded models.

Only enable for unguarded models (Model::unguard() or equivalent). Unsafe if you rely on $fillable allowlists.

To opt in, add the rule to your published rector.php config:

use Jcergolj\RectorForLaravel\CustomRules\RemoveFillableFromUnguardedModelsRector;

return RectorConfig::configure()
    ->withRules([
        RemoveFillableFromUnguardedModelsRector::class,
    ]);

Example:

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected $fillable = [
        'name',
        'email',
    ];
}

becomes:

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
}

RemoveMigrationDownMethodRector

Removes down() methods from Laravel migrations for teams that intentionally do not keep migrations reversible.

Only enable for forward-only migrations. Do not use if your team needs migration rollbacks.

To opt in, add the rule to your published rector.php config:

use Jcergolj\RectorForLaravel\CustomRules\RemoveMigrationDownMethodRector;

return RectorConfig::configure()
    ->withRules([
        RemoveMigrationDownMethodRector::class,
    ]);

Example:

use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    public function up(): void
    {
    }

    public function down(): void
    {
    }
}

becomes:

use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    public function up(): void
    {
    }
}

📄 License

This package is open-sourced software licensed under the MIT license.