Completed
Push — master ( 865800...71ce78 )
by Maarten
01:12
created

LaravelEnvScanner::scan()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 8.7217
c 0
b 0
f 0
cc 6
nc 7
nop 0
1
<?php
2
3
namespace Mtolhuys\LaravelEnvScanner;
4
5
use RecursiveDirectoryIterator;
6
use RecursiveIteratorIterator;
7
use RegexIterator;
8
9
class LaravelEnvScanner
10
{
11
    /**
12
     * The results of performed scan
13
     *
14
     * @var array
15
     */
16
    public $results = [
17
        'files' => 0,
18
        'defined' => 0,
19
        'undefined' => 0,
20
        'depending_on_default' => 0,
21
        'columns' => [],
22
    ];
23
24
    /**
25
     * Stores processed file and var names
26
     *
27
     * @var array
28
     */
29
    private $processed = [
30
        'files' => [],
31
        'vars' => [],
32
    ];
33
34
    /**
35
     * Stores undefined var names
36
     *
37
     * @var array
38
     */
39
    public $undefined = [];
40
41
    /**
42
     * Stores warnings for vars not passing validation
43
     *
44
     * @var array
45
     */
46
    public $warnings = [];
47
48
    /**
49
     * Current file being processed
50
     *
51
     * @var string
52
     */
53
    private $currentFile;
54
55
    /**
56
     * Root directory to start recursive search for env()'s from
57
     * Defaults to config_path()
58
     *
59
     * @var string $dir
60
     */
61
    public $dir;
62
63
    public function __construct(string $dir = null)
64
    {
65
        $this->dir = $dir ?? config_path();
66
    }
67
68
    /**
69
     * Run the scan
70
     *
71
     * @return mixed
72
     * @throws \Exception
73
     */
74
    public function scan()
75
    {
76
        $files = $this->recursiveDirSearch($this->dir, '/.*?.php/');
77
78
        foreach ($files as $file) {
79
            preg_match_all(
80
                '# env\((.*?)\)| getenv\((.*?)\)#',
81
                str_replace(["\n", "\r"], '', file_get_contents($file)),
82
                $matches
83
            );
84
85
            if (empty(array_filter($matches))) {
86
                continue;
87
            }
88
89
            $this->currentFile = $file;
90
            $invocations = $matches[0];
91
92
            foreach ($invocations as $index => $invocation) {
93
                $params = empty($matches[1][$index]) ? $matches[2][$index] : $matches[1][$index];
94
95
                $result = $this->getResult(
96
                    $invocation,
97
                    explode(',', str_replace(["'", '"', ' '], '', $params))
98
                );
99
100
                if (!$result) {
101
                    continue;
102
                }
103
104
                $this->storeResult($result);
105
            }
106
        }
107
108
        return $this;
109
    }
110
111
    /**
112
     * Get result based on comma separated parsed env() or getenv() parameters
113
     * Validates by alphanumeric and underscore and skips already processed
114
     *
115
     * @param string $invocation
116
     * @param array $params
117
     * @return object|bool
118
     */
119
    private function getResult(string $invocation, array $params)
120
    {
121
        $envVar = $params[0];
122
123
        if (in_array($envVar, $this->processed['vars'])) {
124
            return false;
125
        }
126
127
        $this->processed['vars'][] = $envVar;
128
129
        if (!preg_match('/^[A-Za-z0-9_]+$/', $envVar)) {
130
            $invocation = str_replace(' ', '', $invocation);
131
132
            $this->warnings[] = (object)[
133
                'filename' => $this->currentFile,
134
                'invocation' => $invocation,
135
            ];
136
137
            return false;
138
        }
139
140
        return (object)[
141
            'envVar' => $envVar,
142
            'hasValue' => env($envVar) !== null,
143
            'hasDefault' => isset($params[1]),
144
        ];
145
    }
146
147
    /**
148
     * Store result and optional runtime output
149
     *
150
     * @param $result
151
     */
152
    private function storeResult($result)
153
    {
154
        $resultData = [
155
            'filename' => $this->getColumnFilename(),
156
            'defined' => '-',
157
            'depending_on_default' => '-',
158
            'undefined' => '-',
159
        ];
160
161
        if ($result->hasValue) {
162
            $resultData['defined'] = $result->envVar;
163
            $this->results['defined']++;
164
        } else if ($result->hasDefault) {
165
            $resultData['depending_on_default'] = $result->envVar;
166
            $this->results['depending_on_default']++;
167
        } else {
168
            $resultData['undefined'] = $this->undefined[] = $result->envVar;
169
            $this->results['undefined']++;
170
        }
171
172
        $this->results['columns'][] = $resultData;
173
174
        if (!in_array($this->currentFile, $this->processed['files'])) {
175
            $this->results['files']++;
176
            $this->processed['files'][] = $this->currentFile;
177
        }
178
    }
179
180
    /**
181
     * Return filename or '-' for table
182
     *
183
     * @return string
184
     */
185
    private function getColumnFilename(): string
186
    {
187
        if (in_array($this->currentFile, $this->processed['files'])) {
188
            return '-';
189
        }
190
191
        return basename($this->currentFile);
192
    }
193
194
    private function recursiveDirSearch(string $folder, string $pattern): array
195
    {
196
        if (!file_exists($folder)) {
197
            return [];
198
        }
199
200
        $files = new RegexIterator(
201
            new RecursiveIteratorIterator(
202
                new RecursiveDirectoryIterator($folder)
203
            ),
204
            $pattern, RegexIterator::GET_MATCH
205
        );
206
207
        $list = [];
208
209
        foreach ($files as $file) {
210
            $list = array_merge($list, $file);
211
        }
212
213
        return $list;
214
    }
215
}
216