Skip to content

Commit 25cc50a

Browse files
Check command: adds an option to specify an autoloader file to be included (#489)
1 parent 45bd46f commit 25cc50a

File tree

10 files changed

+218
-9
lines changed

10 files changed

+218
-9
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ chmod +x phparkitect.phar
4141
./phparkitect.phar check
4242
```
4343

44+
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]`.
45+
4446
# Usage
4547

4648
To use this tool you need to launch a command via Bash:
@@ -473,6 +475,7 @@ phparkitect check --config=/project/yourConfigFile.php
473475
* `--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.
474476
Supported PHP versions are: 7.4, 8.0, 8.1, 8.2 8.3
475477
* `--stop-on-failure`: With this option the process will end immediately after the first violation.
478+
* `--autoload`: specify the path of an autoload file to be loaded when running phparkitect.
476479

477480
## Run only a specific rule
478481
For some reasons, you might want to run only a specific rule, you can do it using `runOnlyThis` like this:

src/CLI/Command/Check.php

+54-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
use Arkitect\CLI\Baseline;
88
use Arkitect\CLI\ConfigBuilder;
9+
use Arkitect\CLI\Printer\Printer;
910
use Arkitect\CLI\Printer\PrinterFactory;
1011
use Arkitect\CLI\Progress\DebugProgress;
12+
use Arkitect\CLI\Progress\Progress;
1113
use Arkitect\CLI\Progress\ProgressBarProgress;
1214
use Arkitect\CLI\Runner;
1315
use Arkitect\CLI\TargetPhpVersion;
@@ -16,6 +18,7 @@
1618
use Symfony\Component\Console\Input\InputOption;
1719
use Symfony\Component\Console\Output\ConsoleOutputInterface;
1820
use Symfony\Component\Console\Output\OutputInterface;
21+
use Webmozart\Assert\Assert;
1922

2023
class Check extends Command
2124
{
@@ -26,6 +29,7 @@ class Check extends Command
2629
private const SKIP_BASELINE_PARAM = 'skip-baseline';
2730
private const IGNORE_BASELINE_LINENUMBERS_PARAM = 'ignore-baseline-linenumbers';
2831
private const FORMAT_PARAM = 'format';
32+
private const AUTOLOAD_PARAM = 'autoload';
2933

3034
private const GENERATE_BASELINE_PARAM = 'generate-baseline';
3135
private const DEFAULT_RULES_FILENAME = 'phparkitect.php';
@@ -95,6 +99,12 @@ protected function configure(): void
9599
InputOption::VALUE_OPTIONAL,
96100
'Output format: text (default), json, gitlab',
97101
'text'
102+
)
103+
->addOption(
104+
self::AUTOLOAD_PARAM,
105+
'a',
106+
InputOption::VALUE_REQUIRED,
107+
'Specify an autoload file to use',
98108
);
99109
}
100110

@@ -123,20 +133,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
123133
$this->printHeadingLine($output);
124134

125135
$config = ConfigBuilder::loadFromFile($rulesFilename)
136+
->autoloadFilePath($input->getOption(self::AUTOLOAD_PARAM))
126137
->stopOnFailure($stopOnFailure)
127138
->targetPhpVersion(TargetPhpVersion::create($phpVersion))
128139
->baselineFilePath(Baseline::resolveFilePath($useBaseline, self::DEFAULT_BASELINE_FILENAME))
129140
->ignoreBaselineLinenumbers($ignoreBaselineLinenumbers)
130141
->skipBaseline($skipBaseline)
131142
->format($format);
132143

133-
$printer = PrinterFactory::create($config->getFormat());
134-
135-
$progress = $verbose ? new DebugProgress($output) : new ProgressBarProgress($output);
144+
$this->requireAutoload($output, $config->getAutoloadFilePath());
145+
$printer = $this->createPrinter($output, $config->getFormat());
146+
$progress = $this->createProgress($output, $verbose);
147+
$baseline = $this->createBaseline($output, $config->isSkipBaseline(), $config->getBaselineFilePath());
136148

137-
$baseline = Baseline::create($config->isSkipBaseline(), $config->getBaselineFilePath());
138-
139-
$baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found");
140149
$output->writeln("Config file '$rulesFilename' found\n");
141150

142151
$runner = new Runner();
@@ -177,6 +186,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int
177186
}
178187
}
179188

189+
/**
190+
* @psalm-suppress UnresolvableInclude
191+
*/
192+
protected function requireAutoload(OutputInterface $output, ?string $filePath): void
193+
{
194+
if (null === $filePath) {
195+
return;
196+
}
197+
198+
Assert::file($filePath, "Cannot find '$filePath'");
199+
200+
require_once $filePath;
201+
202+
$output->writeln("Autoload file '$filePath' added");
203+
}
204+
205+
protected function createPrinter(OutputInterface $output, string $format): Printer
206+
{
207+
$output->writeln("Output format: $format");
208+
209+
return PrinterFactory::create($format);
210+
}
211+
212+
protected function createProgress(OutputInterface $output, bool $verbose): Progress
213+
{
214+
$output->writeln('Progress: '.($verbose ? 'debug' : 'bar'));
215+
216+
return $verbose ? new DebugProgress($output) : new ProgressBarProgress($output);
217+
}
218+
219+
protected function createBaseline(OutputInterface $output, bool $skipBaseline, ?string $baselineFilePath): Baseline
220+
{
221+
$baseline = Baseline::create($skipBaseline, $baselineFilePath);
222+
223+
$baseline->getFilename() && $output->writeln("Baseline file '{$baseline->getFilename()}' found");
224+
225+
return $baseline;
226+
}
227+
180228
protected function printHeadingLine(OutputInterface $output): void
181229
{
182230
$app = $this->getApplication();

src/CLI/Config.php

+15
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class Config
2727

2828
private string $format;
2929

30+
private ?string $autoloadFilePath;
31+
3032
private TargetPhpVersion $targetPhpVersion;
3133

3234
public function __construct()
@@ -39,6 +41,7 @@ public function __construct()
3941
$this->baselineFilePath = null;
4042
$this->ignoreBaselineLinenumbers = false;
4143
$this->format = PrinterFactory::default();
44+
$this->autoloadFilePath = null;
4245
$this->targetPhpVersion = TargetPhpVersion::latest();
4346
}
4447

@@ -152,4 +155,16 @@ public function isSkipBaseline(): bool
152155
{
153156
return $this->skipBaseline;
154157
}
158+
159+
public function autoloadFilePath(?string $autoloadFilePath): self
160+
{
161+
$this->autoloadFilePath = $autoloadFilePath;
162+
163+
return $this;
164+
}
165+
166+
public function getAutoloadFilePath(): ?string
167+
{
168+
return $this->autoloadFilePath;
169+
}
155170
}

tests/E2E/Cli/CheckCommandTest.php

+16-1
Original file line numberDiff line numberDiff line change
@@ -252,16 +252,27 @@ public function test_gitlab_format_output_no_errors(): void
252252
self::assertJsonStringEqualsJsonString($expectedJson, $cmdTester->getDisplay());
253253
}
254254

255+
public function test_autoload_file(): void
256+
{
257+
$configFilePath = __DIR__.'/../_fixtures/autoload/phparkitect.php';
258+
259+
$cmdTester = $this->runCheck($configFilePath, null, null, false, false, false, 'text', __DIR__.'/../_fixtures/autoload/autoload.php');
260+
261+
self::assertCommandWasSuccessful($cmdTester);
262+
}
263+
255264
protected function runCheck(
256265
$configFilePath = null,
257266
?bool $stopOnFailure = null,
258267
?string $useBaseline = null,
259268
$generateBaseline = false,
260269
bool $skipBaseline = false,
261270
bool $ignoreBaselineNumbers = false,
262-
string $format = 'text'
271+
string $format = 'text',
272+
?string $autoloadFilePath = null
263273
): ApplicationTester {
264274
$input = ['check'];
275+
265276
if (null !== $configFilePath) {
266277
$input['--config'] = $configFilePath;
267278
}
@@ -286,6 +297,10 @@ protected function runCheck(
286297

287298
$input['--format'] = $format;
288299

300+
if ($autoloadFilePath) {
301+
$input['--autoload'] = $autoloadFilePath;
302+
}
303+
289304
$app = new PhpArkitectApplication();
290305
$app->setAutoExit(false);
291306

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
spl_autoload_register(function ($class): void {
5+
$classmap = [
6+
'Autoload\Services\UserService' => __DIR__.'/src/Service/UserService.php',
7+
'Autoload\Model\User' => __DIR__.'/src/Model/User.php',
8+
'Autoload\Model\UserInterface' => __DIR__.'/src/Model/UserInterface.php',
9+
];
10+
11+
$path = $classmap[$class] ?? null;
12+
13+
if (null === $path) {
14+
return;
15+
}
16+
17+
if (!file_exists($path)) {
18+
return;
19+
}
20+
21+
require_once $path;
22+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
use Arkitect\Analyzer\ClassDescription;
5+
use Arkitect\ClassSet;
6+
use Arkitect\CLI\Config;
7+
use Arkitect\Expression\Description;
8+
use Arkitect\Expression\Expression;
9+
use Arkitect\Expression\ForClasses\ResideInOneOfTheseNamespaces;
10+
use Arkitect\Rules\Rule;
11+
use Arkitect\Rules\Violation;
12+
use Arkitect\Rules\ViolationMessage;
13+
use Arkitect\Rules\Violations;
14+
15+
return static function (Config $config): void {
16+
// a dummy rule to check if the class is autoloaded
17+
// is_a with 'true' passed as the third parameter triggers the autoloader
18+
$autoload_rule = new class('Autoload\Model\UserInterface') implements Expression {
19+
public string $implements;
20+
21+
public function __construct(string $implements)
22+
{
23+
$this->implements = $implements;
24+
}
25+
26+
public function describe(ClassDescription $theClass, string $because): Description
27+
{
28+
return new Description("{$theClass->getFQCN()} should implement {$this->implements}", $because);
29+
}
30+
31+
public function evaluate(ClassDescription $theClass, Violations $violations, string $because): void
32+
{
33+
if (is_a($theClass->getFQCN(), $this->implements, true)) {
34+
return;
35+
}
36+
37+
$violation = Violation::create(
38+
$theClass->getFQCN(),
39+
ViolationMessage::selfExplanatory($this->describe($theClass, $because)),
40+
$theClass->getFilePath()
41+
);
42+
43+
$violations->add($violation);
44+
}
45+
};
46+
47+
$class_set = ClassSet::fromDir(__DIR__.'/src');
48+
49+
$rule = Rule::allClasses()
50+
->except('Autoload\Model\UserInterface')
51+
->that(new ResideInOneOfTheseNamespaces('Autoload\Model'))
52+
->should($autoload_rule)
53+
->because('we want check if the class is autoloaded');
54+
55+
$config
56+
->add($class_set, $rule);
57+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Autoload\Model;
5+
6+
class User implements UserInterface
7+
{
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Autoload\Model;
5+
6+
interface UserInterface
7+
{
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Autoload\Services;
5+
6+
class UserService
7+
{
8+
}

tests/Unit/CLI/Printer/GitlabPrinterTest.php

+27-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,33 @@ public function test_print_with_violations(): void
2727

2828
$result = $printer->print($violationsCollection);
2929

30-
self::assertSame(<<<JSON
31-
[{"description":"Some error message","check_name":"RuleA.some-error-message","fingerprint":"7ddcfd42f5f2af3d00864ef959a0327f508cb5227aedca96d919d681a5dcde4a","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":42}}},{"description":"Another error message","check_name":"RuleB.another-error-message","fingerprint":"800c2ceafbf4023e401200186ecabdfe59891c5d6670e86571e3c50339df07dc","severity":"major","location":{"path":"tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php","lines":{"begin":1}}}]
30+
self::assertJsonStringEqualsJsonString(<<<JSON
31+
[
32+
{
33+
"description": "Some error message",
34+
"check_name": "RuleA.some-error-message",
35+
"fingerprint": "7ddcfd42f5f2af3d00864ef959a0327f508cb5227aedca96d919d681a5dcde4a",
36+
"severity": "major",
37+
"location": {
38+
"path": "tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php",
39+
"lines": {
40+
"begin": 42
41+
}
42+
}
43+
},
44+
{
45+
"description": "Another error message",
46+
"check_name": "RuleB.another-error-message",
47+
"fingerprint": "800c2ceafbf4023e401200186ecabdfe59891c5d6670e86571e3c50339df07dc",
48+
"severity": "major",
49+
"location": {
50+
"path": "tests\/Unit\/CLI\/Printer\/GitlabPrinterTest.php",
51+
"lines": {
52+
"begin": 1
53+
}
54+
}
55+
}
56+
]
3257
JSON, $result);
3358
}
3459

0 commit comments

Comments
 (0)