Skip to content
7 changes: 7 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
],
\BeyondCode\SelfDiagnosis\Checks\EnvFileExists::class,
\BeyondCode\SelfDiagnosis\Checks\ExampleEnvironmentVariablesAreSet::class,
\BeyondCode\SelfDiagnosis\Checks\UsedEnvironmentVariablesAreDefined::class => [
'directories' => [
config_path(),
app_path(),
],
],
\BeyondCode\SelfDiagnosis\Checks\LocalesAreInstalled::class => [
'required_locales' => [
'en_US',
Expand Down Expand Up @@ -65,6 +71,7 @@
\BeyondCode\SelfDiagnosis\Checks\ConfigurationIsNotCached::class,
\BeyondCode\SelfDiagnosis\Checks\RoutesAreNotCached::class,
\BeyondCode\SelfDiagnosis\Checks\ExampleEnvironmentVariablesAreUpToDate::class,
\BeyondCode\SelfDiagnosis\Checks\UsedEnvironmentVariablesAreDefined::class,
],
'production' => [
\BeyondCode\SelfDiagnosis\Checks\ComposerWithoutDevDependenciesIsUpToDate::class,
Expand Down
167 changes: 167 additions & 0 deletions src/Checks/UsedEnvironmentVariablesAreDefined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?php

namespace BeyondCode\SelfDiagnosis\Checks;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RegexIterator;

class UsedEnvironmentVariablesAreDefined implements Check
{
/**
* Stores processed var names
*
* @var array
*/
private $processed = [];

/**
* Stores undefined var names
*
* @var array
*/
public $undefined = [];

/**
* The amount of undefined .env variables
*
* @var integer
*/
public $amount = 0;

/**
* The name of the check.
*
* @param array $config
* @return string
*/
public function name(array $config): string
{
return trans('self-diagnosis::checks.used_env_variables_are_defined.name');
}

/**
* The error message to display in case the check does not pass.
*
* @param array $config
* @return string
*/
public function message(array $config): string
{
return trans('self-diagnosis::checks.used_env_variables_are_defined.message', [
'amount' => $this->amount,
'undefined' => implode(PHP_EOL, $this->undefined),
]);
}

/**
* Perform the actual verification of this check.
*
* @param array $config
* @return bool
* @throws \Exception
*/
public function check(array $config): bool
{
$paths = Collection::make(Arr::get($config, 'directories', []));

foreach ($paths as $path) {
$files = $this->recursiveDirSearch($path, '/.*?.php/');

foreach ($files as $file) {
preg_match_all(
'# env\((.*?)\)| getenv\((.*?)\)#',
str_replace(["\n", "\r"], '', file_get_contents($file)),
$values
);

$values = array_filter(
array_merge($values[1], $values[2])
);

foreach ($values as $value) {
$result = $this->getResult(
explode(',', str_replace(["'", '"', ' '], '', $value))
);

if (!$result) {
continue;
}

$this->storeResult($result);
}
}
}

return $this->amount === 0;
}

/**
* Get result based on comma separated env() or getenv() parameters
*
* @param array $values
* @return object|bool
*/
private function getResult(array $values)
{
$envVar = $values[0];

if (in_array($envVar, $this->processed, true)) {
return false;
}

$this->processed[] = $envVar;

return (object)[
'envVar' => $envVar,
'hasValue' => env($envVar) !== null,
'hasDefault' => isset($values[1]),
];
}

/**
* Store result based on getResult's return value
*
* @param $result
*/
private function storeResult($result)
{
if (!$result->hasValue && !$result->hasDefault) {
$this->undefined[] = $result->envVar;
$this->amount++;
}
}

/**
* Recursively search folder(s) for files matching pattern
*
* @param string $folder
* @param string $pattern
* @return array
*/
private function recursiveDirSearch(string $folder, string $pattern): array
{
if (!file_exists($folder)) {
return [];
}

$files = new RegexIterator(
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($folder)
),
$pattern, RegexIterator::GET_MATCH
);

$list = [[]];

foreach ($files as $file) {
$list[] = $file;
}

$list = array_merge(...$list);

return $list;
}
}
55 changes: 55 additions & 0 deletions tests/UsedEnvironmentVariablesAreDefinedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace BeyondCode\SelfDiagnosis\Tests;

use BeyondCode\SelfDiagnosis\Checks\UsedEnvironmentVariablesAreDefined;
use BeyondCode\SelfDiagnosis\SelfDiagnosisServiceProvider;
use Orchestra\Testbench\TestCase;

class UsedEnvironmentVariablesAreDefinedTest extends TestCase
{
public function getPackageProviders($app)
{
return [
SelfDiagnosisServiceProvider::class,
];
}

/** @test
* @throws \Exception
*/
public function it_checks_if_used_env_vars_are_defined()
{
env('FILLED');
env('NOT_FILLED');
env('FILLED_WITH_FALSE');
getenv('GET_FILLED');

env('DEPENDING_ON_DEFAULT', 'default');
env('DEFAULT_IS_FALSE', false);
getenv('GET_DEPENDING_ON_DEFAULT', 'default');

env('UNDEFINED');
getenv('GET_UNDEFINED');
// Doubles should be ignored
env('UNDEFINED');
getenv('GET_UNDEFINED');

$config = [
'directories' => [
__DIR__
],
];

$check = new UsedEnvironmentVariablesAreDefined();

$this->assertFalse($check->check($config));
$this->assertSame($check->amount, 2);
$this->assertContains('UNDEFINED', $check->undefined);
$this->assertContains('GET_UNDEFINED', $check->undefined);
$this->assertSame(
"2 used environmental variables are undefined: \nUNDEFINED\nGET_UNDEFINED",
$check->message($config)
);
}
}
Loading