diff --git a/docs/rules.md b/docs/rules.md index efb217511..285804ebf 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -356,3 +356,31 @@ parameters: - `@includeUnless` Blade directive. - `@includeWhen` Blade directive. - `@includeFirst` Blade directive. + +## NoEnvCallsOutsideOfConfig + +Checks for `env` calls out side of the config directory which returns null +when the config is cached. + +#### Examples + +```php +env(...); +``` + +Will result in the following error: + +``` +Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.") +``` + +#### Configuration + +This rule is disabled by default. To enable, add: + +```neon +parameters: + noEnvCallsOutsideOfConfig: true +``` + +to your `phpstan.neon` file. diff --git a/extension.neon b/extension.neon index 5400f71d9..d8acfc91f 100644 --- a/extension.neon +++ b/extension.neon @@ -12,6 +12,7 @@ parameters: bootstrapFiles: - bootstrap.php checkOctaneCompatibility: false + noEnvCallsOutsideOfConfig: false noModelMake: true noUnnecessaryCollectionCall: true noUnnecessaryCollectionCallOnly: [] @@ -27,6 +28,7 @@ parameters: parametersSchema: checkOctaneCompatibility: bool() + noEnvCallsOutsideOfConfig: bool() noModelMake: bool() noUnnecessaryCollectionCall: bool() noUnnecessaryCollectionCallOnly: listOf(string()) @@ -40,6 +42,8 @@ parametersSchema: checkUnusedViews: bool() conditionalTags: + Larastan\Larastan\Rules\NoEnvCallsOutsideOfConfigRule: + phpstan.rules.rule: %noEnvCallsOutsideOfConfig% Larastan\Larastan\Rules\NoModelMakeRule: phpstan.rules.rule: %noModelMake% Larastan\Larastan\Rules\NoUnnecessaryCollectionCallRule: @@ -380,6 +384,9 @@ services: - class: Larastan\Larastan\Rules\OctaneCompatibilityRule + - + class: Larastan\Larastan\Rules\NoEnvCallsOutsideOfConfigRule + - class: Larastan\Larastan\Rules\NoModelMakeRule diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b3da3c5cd..5fd721659 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,18 +1,17 @@ - - - ./tests - - + + + ./tests + + diff --git a/src/Rules/NoEnvCallsOutsideOfConfigRule.php b/src/Rules/NoEnvCallsOutsideOfConfigRule.php new file mode 100644 index 000000000..70ee21da4 --- /dev/null +++ b/src/Rules/NoEnvCallsOutsideOfConfigRule.php @@ -0,0 +1,60 @@ + + */ +class NoEnvCallsOutsideOfConfigRule implements Rule +{ + use HasContainer; + + public function getNodeType(): string + { + return FuncCall::class; + } + + /** @return array */ + public function processNode(Node $node, Scope $scope): array + { + $name = $node->name; + + if (! $name instanceof Name) { + return []; + } + + if ($scope->resolveName($name) !== 'env') { + return []; + } + + if (! $this->isCalledOutsideOfConfig($node, $scope)) { + return []; + } + + return [ + RuleErrorBuilder::message("Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.") + ->identifier('rules.noEnvCallsOutsideOfConfig') + ->line($node->getLine()) + ->file($scope->getFile()) + ->build(), + ]; + } + + protected function isCalledOutsideOfConfig(FuncCall $call, Scope $scope): bool + { + return str_starts_with($scope->getFile(), config_path()) === false; + } +} diff --git a/tests/Rules/NoEnvCallsOutsideOfConfigRuleTest.php b/tests/Rules/NoEnvCallsOutsideOfConfigRuleTest.php new file mode 100644 index 000000000..15e5fe92c --- /dev/null +++ b/tests/Rules/NoEnvCallsOutsideOfConfigRuleTest.php @@ -0,0 +1,58 @@ + */ +class NoEnvCallsOutsideOfConfigRuleTest extends RuleTestCase +{ + protected function setUp(): void + { + $this->overrideConfigPath(__DIR__.'/data/config'); + } + + protected function getRule(): Rule + { + return new NoEnvCallsOutsideOfConfigRule(); + } + + /** @test */ + public function itDoesNotFailForEnvCallsInsideConfigDirectory(): void + { + $this->analyse([__DIR__.'/data/config/env-calls.php'], []); + } + + /** @test */ + public function itReportsEnvCallsOutsideOfConfigDirectory(): void + { + $this->analyse([__DIR__.'/data/env-calls.php'], [ + ["Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.", 7], + ["Called 'env' outside of the config directory which returns null when the config is cached, use 'config'.", 8], + ]); + } + + + protected function overrideConfigPath(string $path): void + { + $app = Application::getInstance(); + + if (version_compare(LARAVEL_VERSION, '10.0.0', '>=')) { + $app->useConfigPath($path); + + return; + } + + $reflectionClass = new ReflectionClass($app); + $property = $reflectionClass->getProperty('basePath'); + $property->setAccessible(true); + + $property->setValue($app, str_replace('/config', '', $path)); + } +} diff --git a/tests/Rules/data/config/env-calls.php b/tests/Rules/data/config/env-calls.php new file mode 100644 index 000000000..5aa945c56 --- /dev/null +++ b/tests/Rules/data/config/env-calls.php @@ -0,0 +1,6 @@ +