Skip to content

Repository files navigation

Swiss Knife for Upgrades

Downloads total

Swiss knife in the pocket of every upgrade architect!


Install

composer require rector/swiss-knife --dev



Usage


1. Check your Code for Git Merge Conflicts

Do you use Git? Then merge conflict markers are the last thing you want to see in pushed code:

<<<<<<< HEAD

Add this command to CI to spot these:

vendor/bin/swiss-knife check-conflicts .

You can skip paths with the --exclude option:

vendor/bin/swiss-knife check-conflicts . --exclude vendor --exclude tests/fixtures

2. Detect Commented Code

Have you ever forgotten commented code in your project?

//      foreach ($matches as $match) {
//           $content = str_replace($match[0], $match[2], $content);
//      }

No more! Add this command to CI to spot these:

vendor/bin/swiss-knife check-commented-code <directory>
vendor/bin/swiss-knife check-commented-code packages --line-limit 5 --skip-file '*Controller.php'

3. Reach full PSR-4

Find multiple classes in single file

To make PSR-4 work properly, each class must be in its own file. This command makes it easy to spot multiple classes in a single file:

vendor/bin/swiss-knife find-multi-classes src

Update Namespace to match PSR-4 Root

Is your class in wrong namespace? Make it match your PSR-4 root:

vendor/bin/swiss-knife namespace-to-psr-4 src --namespace-root "App\\"

This will update all files in your /src directory, to starts with App\\ and follow full PSR-4 path:

 # file path: src/Repository/TalkRepository.php

-namespace Model;
+namespace App\Repository;

 ...

4. Finalize classes without children

Do you want to finalize all classes that don't have children?

vendor/bin/swiss-knife finalize-classes src tests

Do you use mocks but not bypass final yet?

vendor/bin/swiss-knife finalize-classes src tests --skip-mocked

This will keep mocked classes non-final, so PHPUnit can extend them internally.


Do you want to skip file or two?

vendor/bin/swiss-knife finalize-classes src tests --skip-file src/SpecialProxy.php

Skip is also support with fnmatch() patterns:

vendor/bin/swiss-knife finalize-classes src tests --skip-file '*Controller.php'

5. Privatize local class constants

PHPStan can report unused private class constants, but it skips all the public ones. Do you have lots of class constants, all of them public but want to narrow scope to privates?

vendor/bin/swiss-knife privatize-constants src test

This command will:

  • find all class constant usages
  • scans classes and constants
  • makes those constant used locally private

That way all the constants not used outside will be made private safely.


6. Split huge Symfony config to per-package in directory

Do you have a huge Symfony config file that is hard to navigate? Do you want to split it to per-package files?

Before - one huge config/config_dev.php with many extensions in a single file:

<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('framework', [
        'secret' => '%env(APP_SECRET)%',
        'test' => true,
    ]);

    $containerConfigurator->extension('doctrine', [
        'dbal' => [
            'url' => '%env(DATABASE_URL)%',
        ],
    ]);

    $containerConfigurator->extension('monolog', [
        'handlers' => [
            'main' => [
                'type' => 'stream',
                'path' => '%kernel.logs_dir%/%kernel.environment%.log',
            ],
        ],
    ]);
};

Run the command:

vendor/bin/swiss-knife split-config-per-package config/config_dev.php --output-dir config/packages/dev

After - the original config only imports the per-package files:

<?php

declare(strict_types=1);

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->import(__DIR__ . '/packages/dev/*');
};

And each extension lives in its own file, e.g. config/packages/dev/doctrine.php:

<?php

declare(strict_types=1);

return static function (Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->extension('doctrine', [
        'dbal' => [
            'url' => '%env(DATABASE_URL)%',
        ],
    ]);
};

All the extensions will be extracted to separate files in config/packages/dev directory, making them much more readable.


7. Generate Symfony Smoke Tests

Cover your Symfony app with smoke tests in seconds. This command scans your composer.json, picks the matching test templates, and drops them under tests/Unit/Smoke (or your project's equivalent unit-tests directory).

vendor/bin/swiss-knife generate-symfony-smoke-tests

The command will:

  • detect your unit tests directory and create a Smoke sub-directory
  • generate a ServiceContainerTest that boots the kernel and instantiates every service to catch container misconfiguration early
  • add a shared AbstractContainerTestCase with a typed getService() helper
  • adjust the namespace and Kernel class in the templates to match your project (uses App\Kernel, AppKernel, or Kernel, whichever exists)

Existing files are never overwritten, so the command is safe to re-run.

The generated ServiceContainerTest boots the kernel and asserts every service can be instantiated:

<?php

declare(strict_types=1);

namespace App\Tests\Unit\Smoke;

use Throwable;

final class ServiceContainerTest extends AbstractContainerTestCase
{
    public function testServiceConstruction(): void
    {
        $serviceIds = self::$container->getServiceIds();

        $checkedServiceCount = 0;

        foreach ($serviceIds as $serviceId) {
            if ($this->isDynamicService($serviceId)) {
                continue;
            }

            try {
                self::$container->get($serviceId);
            } catch (Throwable $throwable) {
                $this->fail(sprintf('Service "%s" could not be created because:%s%s', $serviceId, PHP_EOL, $throwable->getMessage()));
            }

            ++$checkedServiceCount;
        }

        // @todo update this number to match your service count
        $this->assertSame(100000, $checkedServiceCount);
    }

    private function isDynamicService(string $serviceId): bool
    {
        if (str_contains($serviceId, 'session')) {
            return true;
        }

        if (str_starts_with($serviceId, 'doctrine.')) {
            return true;
        }

        return in_array(
            $serviceId,
            ['kernel', 'database_connection', 'event_dispatcher'],
            true
        );
    }
}

8. Detect Duplicated Code

Spot copy-pasted code blocks with a token-based detector, a small clone of phpcpd. Add it to CI to fail when a large copy-pasted block is added:

vendor/bin/swiss-knife duplicated-code src
vendor/bin/swiss-knife duplicated-code src rules --min-tokens 150 --min-lines 5

Options:

  • --min-lines minimum lines of a reported clone (default 5)
  • --min-tokens minimum tokens of a reported clone (default 70)
  • --fuzzy ignore variable names when matching
  • --skip-file file paths or masks to skip

Exit code is 1 when clones are found, 0 otherwise.


10. Measure Code Size

Measure lines of code and structure size of your project - files, classes, methods, constants and more:

vendor/bin/swiss-knife measure src
vendor/bin/swiss-knife measure src tests --exclude tests/fixtures

Options:

  • --exclude paths to exclude
  • --short print short metrics only
  • --longest show top 10 longest files
  • --allow-vendor allow the /vendor directory to be scanned
  • --json output in JSON format

11. Count PHP Features

Count PHP features used in the project, grouped by the PHP version that introduced them. Handy to see how modern your code is before an upgrade:

vendor/bin/swiss-knife features src

Options:

  • --json output in JSON format

That's it!


Happy coding!

About

Swiss knife in pocket of every upgrade architect!

Topics

Resources

Stars

211 stars

Watchers

4 watching

Forks

Releases

Used by

Contributors

Languages