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 @@
+