diff --git a/README.md b/README.md index b2c194ec..27e65019 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,8 @@ chmod +x phparkitect.phar ./phparkitect.phar check ``` + When you run phparkitect as phar and you have custom rules in need of autoloading the project classes you'll need to specify the option `--autoload=[AUTOLOAD_FILE]`. + # Usage To use this tool you need to launch a command via Bash: @@ -473,6 +475,7 @@ phparkitect check --config=/project/yourConfigFile.php * `--target-php-version`: With this parameter, you can specify which PHP version should use the parser. This can be useful to debug problems and to understand if there are problems with a different PHP version. Supported PHP versions are: 7.4, 8.0, 8.1, 8.2 8.3 * `--stop-on-failure`: With this option the process will end immediately after the first violation. + * `--autoload`: specify the path of an autoload file to be loaded when running phparkitect. ## Run only a specific rule For some reasons, you might want to run only a specific rule, you can do it using `runOnlyThis` like this: diff --git a/src/CLI/Command/Check.php b/src/CLI/Command/Check.php index d42c4c78..080ac6e2 100644 --- a/src/CLI/Command/Check.php +++ b/src/CLI/Command/Check.php @@ -6,8 +6,10 @@ use Arkitect\CLI\Baseline; use Arkitect\CLI\ConfigBuilder; +use Arkitect\CLI\Printer\Printer; use Arkitect\CLI\Printer\PrinterFactory; use Arkitect\CLI\Progress\DebugProgress; +use Arkitect\CLI\Progress\Progress; use Arkitect\CLI\Progress\ProgressBarProgress; use Arkitect\CLI\Runner; use Arkitect\CLI\TargetPhpVersion; @@ -16,6 +18,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Webmozart\Assert\Assert; class Check extends Command { @@ -26,6 +29,7 @@ class Check extends Command private const SKIP_BASELINE_PARAM = 'skip-baseline'; private const IGNORE_BASELINE_LINENUMBERS_PARAM = 'ignore-baseline-linenumbers'; private const FORMAT_PARAM = 'format'; + private const AUTOLOAD_PARAM = 'autoload'; private const GENERATE_BASELINE_PARAM = 'generate-baseline'; private const DEFAULT_RULES_FILENAME = 'phparkitect.php'; @@ -95,6 +99,12 @@ protected function configure(): void InputOption::VALUE_OPTIONAL, 'Output format: text (default), json, gitlab', 'text' + ) + ->addOption( + self::AUTOLOAD_PARAM, + 'a', + InputOption::VALUE_REQUIRED, + 'Specify an autoload file to use', ); } @@ -123,6 +133,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->printHeadingLine($output); $config = ConfigBuilder::loadFromFile($rulesFilename) + ->autoloadFilePath($input->getOption(self::AUTOLOAD_PARAM)) ->stopOnFailure($stopOnFailure) ->targetPhpVersion(TargetPhpVersion::create($phpVersion)) ->baselineFilePath(Baseline::resolveFilePath($useBaseline, self::DEFAULT_BASELINE_FILENAME)) @@ -130,13 +141,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->skipBaseline($skipBaseline) ->format($format); - $printer = PrinterFactory::create($config->getFormat()); - - $progress = $verbose ? new DebugProgress($output) : new ProgressBarProgress($output); + $this->requireAutoload($output, $config->getAutoloadFilePath()); + $printer = $this->createPrinter($output, $config->getFormat()); + $progress = $this->createProgress($output, $verbose); + $baseline = $this->createBaseline($output, $config->isSkipBaseline(), $config->getBaselineFilePath()); - $baseline = Baseline::create($config->isSkipBaseline(), $config->getBaselineFilePath()); - - $baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found"); $output->writeln("Config file '$rulesFilename' found\n"); $runner = new Runner(); @@ -177,6 +186,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + /** + * @psalm-suppress UnresolvableInclude + */ + protected function requireAutoload(OutputInterface $output, ?string $filePath): void + { + if (null === $filePath) { + return; + } + + Assert::file($filePath, "Cannot find '$filePath'"); + + require_once $filePath; + + $output->writeln("Autoload file '$filePath' added"); + } + + protected function createPrinter(OutputInterface $output, string $format): Printer + { + $output->writeln("Output format: $format"); + + return PrinterFactory::create($format); + } + + protected function createProgress(OutputInterface $output, bool $verbose): Progress + { + $output->writeln('Progress: '.($verbose ? 'debug' : 'bar')); + + return $verbose ? new DebugProgress($output) : new ProgressBarProgress($output); + } + + protected function createBaseline(OutputInterface $output, bool $skipBaseline, ?string $baselineFilePath): Baseline + { + $baseline = Baseline::create($skipBaseline, $baselineFilePath); + + $baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found"); + + return $baseline; + } + protected function printHeadingLine(OutputInterface $output): void { $app = $this->getApplication(); diff --git a/src/CLI/Config.php b/src/CLI/Config.php index 0305a313..361a16aa 100644 --- a/src/CLI/Config.php +++ b/src/CLI/Config.php @@ -27,6 +27,8 @@ class Config private string $format; + private ?string $autoloadFilePath; + private TargetPhpVersion $targetPhpVersion; public function __construct() @@ -39,6 +41,7 @@ public function __construct() $this->baselineFilePath = null; $this->ignoreBaselineLinenumbers = false; $this->format = PrinterFactory::default(); + $this->autoloadFilePath = null; $this->targetPhpVersion = TargetPhpVersion::latest(); } @@ -152,4 +155,16 @@ public function isSkipBaseline(): bool { return $this->skipBaseline; } + + public function autoloadFilePath(?string $autoloadFilePath): self + { + $this->autoloadFilePath = $autoloadFilePath; + + return $this; + } + + public function getAutoloadFilePath(): ?string + { + return $this->autoloadFilePath; + } } diff --git a/tests/E2E/Cli/CheckCommandTest.php b/tests/E2E/Cli/CheckCommandTest.php index d4569e74..8d6db7e9 100644 --- a/tests/E2E/Cli/CheckCommandTest.php +++ b/tests/E2E/Cli/CheckCommandTest.php @@ -252,6 +252,15 @@ public function test_gitlab_format_output_no_errors(): void self::assertJsonStringEqualsJsonString($expectedJson, $cmdTester->getDisplay()); } + public function test_autoload_file(): void + { + $configFilePath = __DIR__.'/../_fixtures/autoload/phparkitect.php'; + + $cmdTester = $this->runCheck($configFilePath, null, null, false, false, false, 'text', __DIR__.'/../_fixtures/autoload/autoload.php'); + + self::assertCommandWasSuccessful($cmdTester); + } + protected function runCheck( $configFilePath = null, ?bool $stopOnFailure = null, @@ -259,9 +268,11 @@ protected function runCheck( $generateBaseline = false, bool $skipBaseline = false, bool $ignoreBaselineNumbers = false, - string $format = 'text' + string $format = 'text', + ?string $autoloadFilePath = null ): ApplicationTester { $input = ['check']; + if (null !== $configFilePath) { $input['--config'] = $configFilePath; } @@ -286,6 +297,10 @@ protected function runCheck( $input['--format'] = $format; + if ($autoloadFilePath) { + $input['--autoload'] = $autoloadFilePath; + } + $app = new PhpArkitectApplication(); $app->setAutoExit(false); diff --git a/tests/E2E/_fixtures/autoload/autoload.php b/tests/E2E/_fixtures/autoload/autoload.php new file mode 100644 index 00000000..9f1d8bb7 --- /dev/null +++ b/tests/E2E/_fixtures/autoload/autoload.php @@ -0,0 +1,22 @@ + __DIR__.'/src/Service/UserService.php', + 'Autoload\Model\User' => __DIR__.'/src/Model/User.php', + 'Autoload\Model\UserInterface' => __DIR__.'/src/Model/UserInterface.php', + ]; + + $path = $classmap[$class] ?? null; + + if (null === $path) { + return; + } + + if (!file_exists($path)) { + return; + } + + require_once $path; +}); diff --git a/tests/E2E/_fixtures/autoload/phparkitect.php b/tests/E2E/_fixtures/autoload/phparkitect.php new file mode 100644 index 00000000..7b929d2e --- /dev/null +++ b/tests/E2E/_fixtures/autoload/phparkitect.php @@ -0,0 +1,57 @@ +implements = $implements; + } + + public function describe(ClassDescription $theClass, string $because): Description + { + return new Description("{$theClass->getFQCN()} should implement {$this->implements}", $because); + } + + public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void + { + if (is_a($theClass->getFQCN(), $this->implements, true)) { + return; + } + + $violation = Violation::create( + $theClass->getFQCN(), + ViolationMessage::selfExplanatory($this->describe($theClass, $because)), + $theClass->getFilePath() + ); + + $violations->add($violation); + } + }; + + $class_set = ClassSet::fromDir(__DIR__.'/src'); + + $rule = Rule::allClasses() + ->except('Autoload\Model\UserInterface') + ->that(new ResideInOneOfTheseNamespaces('Autoload\Model')) + ->should($autoload_rule) + ->because('we want check if the class is autoloaded'); + + $config + ->add($class_set, $rule); +}; diff --git a/tests/E2E/_fixtures/autoload/src/Model/User.php b/tests/E2E/_fixtures/autoload/src/Model/User.php new file mode 100644 index 00000000..2cd0d00d --- /dev/null +++ b/tests/E2E/_fixtures/autoload/src/Model/User.php @@ -0,0 +1,8 @@ +print($violationsCollection); - self::assertSame(<<