Skip to content

Commit

Permalink
feat: detect usage of env() function outside of config folder (#1828)
Browse files Browse the repository at this point in the history
  • Loading branch information
calebdw committed Feb 11, 2024
1 parent 52dd428 commit 7b01d2e
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 7 deletions.
28 changes: 28 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 7 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ parameters:
bootstrapFiles:
- bootstrap.php
checkOctaneCompatibility: false
noEnvCallsOutsideOfConfig: false
noModelMake: true
noUnnecessaryCollectionCall: true
noUnnecessaryCollectionCallOnly: []
Expand All @@ -27,6 +28,7 @@ parameters:

parametersSchema:
checkOctaneCompatibility: bool()
noEnvCallsOutsideOfConfig: bool()
noModelMake: bool()
noUnnecessaryCollectionCall: bool()
noUnnecessaryCollectionCallOnly: listOf(string())
Expand All @@ -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:
Expand Down Expand Up @@ -380,6 +384,9 @@ services:
-
class: Larastan\Larastan\Rules\OctaneCompatibilityRule

-
class: Larastan\Larastan\Rules\NoEnvCallsOutsideOfConfigRule

-
class: Larastan\Larastan\Rules\NoModelMakeRule

Expand Down
13 changes: 6 additions & 7 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
beStrictAboutOutputDuringTests="true"
beStrictAboutChangesToGlobalState="true"
bootstrap="phpunit-bootstrap.php"
colors="true"
failOnRisky="true"
failOnWarning="true"
verbose="true"
>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>
60 changes: 60 additions & 0 deletions src/Rules/NoEnvCallsOutsideOfConfigRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace Larastan\Larastan\Rules;

use Larastan\Larastan\Concerns\HasContainer;
use PhpParser\Node;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Name;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleError;
use PHPStan\Rules\RuleErrorBuilder;

/**
* Catches `env()` calls outside of the config directory.
*
* @implements Rule<FuncCall>
*/
class NoEnvCallsOutsideOfConfigRule implements Rule
{
use HasContainer;

public function getNodeType(): string
{
return FuncCall::class;
}

/** @return array<int, RuleError> */
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;
}
}
58 changes: 58 additions & 0 deletions tests/Rules/NoEnvCallsOutsideOfConfigRuleTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

declare(strict_types=1);

namespace Tests\Rules;

use Illuminate\Foundation\Application;
use Larastan\Larastan\Rules\NoEnvCallsOutsideOfConfigRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
use ReflectionClass;

/** @extends RuleTestCase<NoEnvCallsOutsideOfConfigRule> */
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));
}
}
6 changes: 6 additions & 0 deletions tests/Rules/data/config/env-calls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php

namespace Tests\Rules\Data;

env('foo');
\env('bar');
12 changes: 12 additions & 0 deletions tests/Rules/data/env-calls.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Tests\Rules\Data;

use function Foo\Bar\env as scopedEnv;

env('foo');
\env('bar');

// no report for namespaced calls
\Foo\Bar\env('bar');
scopedEnv('foo');

0 comments on commit 7b01d2e

Please sign in to comment.