ApiFactoryAbstract::environmentVariableExists()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace GinoPane\PHPolyglot\API\Factory;
4
5
use Dotenv\Dotenv;
6
use GinoPane\PHPolyglot\Exception\InvalidPathException;
7
use GinoPane\PHPolyglot\Exception\InvalidConfigException;
8
use GinoPane\PHPolyglot\Exception\InvalidApiClassException;
9
10
/**
11
 * Interface ApiFactoryAbstract
12
 * Abstract class that provides a method to get the necessary API object
13
 *
14
 * @author Sergey <Gino Pane> Karavay
15
 */
16
abstract class ApiFactoryAbstract implements ApiFactoryInterface
17
{
18
    /**
19
     * Environment file name
20
     */
21
    const ENV_FILE_NAME = ".env";
22
23
    /**
24
     * Config file name
25
     */
26
    const CONFIG_FILE_NAME = "config.php";
27
28
    /**
29
     * Config section name that is being checked for existence. API-specific properties must
30
     * be located under that section
31
     *
32
     * @var string
33
     */
34
    protected $configSectionName = "";
35
36
    /**
37
     * @var array|null
38
     */
39
    protected static $config = null;
40
41
    /**
42
     * Boolean configuration flag which indicates whether environment variables were set or not
43
     *
44
     * @var array|null
45
     */
46
    protected static $envIsSet = false;
47
48
    /**
49
     * Config properties that must exist for valid config
50
     *
51
     * @var array
52
     */
53
    protected $configProperties = [
54
        'default'
55
    ];
56
57
    /**
58
     * API interface that must be implemented by API class
59
     *
60
     * @var string
61
     */
62
    protected $apiInterface = "";
63
64
    /**
65
     * ApiFactoryAbstract constructor
66
     *
67
     * @throws InvalidPathException
68
     * @throws InvalidConfigException
69
     * @throws InvalidApiClassException
70
     */
71
    public function __construct()
72
    {
73
        if (is_null(self::$config)) {
74
            $this->initConfig();
75
        }
76
77
        if (!self::$envIsSet) {
78
            $this->initEnvironment();
79
        }
80
81
        $this->assertConfigIsValid();
82
    }
83
84
    /**
85
     * Gets necessary API object
86
     *
87
     * @param array $parameters
88
     *
89
     * @return mixed
90
     */
91
    public function getApi(array $parameters = [])
92
    {
93
        $apiClass = $this->getFactorySpecificConfig()['default'];
94
        
95
        return new $apiClass($parameters);
96
    }
97
98
    /**
99
     * @param array $config
100
     */
101
    public static function setConfig(array $config = [])
102
    {
103
        self::$config = $config;
104
    }
105
106
    /**
107
     * Sets environment variables using $env array. Existing variables will not be overwritten
108
     *
109
     * @param array $env
110
     */
111
    public static function setEnv(array $env = [])
112
    {
113
        self::$envIsSet = true;
0 ignored issues
show
Documentation Bug introduced by
It seems like true of type true is incompatible with the declared type null|array of property $envIsSet.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
114
115
        foreach ($env as $variable => $value) {
116
            self::setEnvironmentVariable($variable, $value);
117
        }
118
    }
119
120
    /**
121
     * @param   $name
122
     * @param   $value
123
     */
124
    private static function setEnvironmentVariable($name, $value = null)
125
    {
126
        if (self::environmentVariableExists($name)) {
127
            return;
128
        }
129
130
        if (function_exists('putenv')) {
131
            putenv("$name=$value");
132
        }
133
134
        $_ENV[$name] = $value;
135
        $_SERVER[$name] = $value;
136
    }
137
138
    /**
139
     * @param $name
140
     *
141
     * @return mixed
142
     */
143
    private static function environmentVariableExists($name)
144
    {
145
        switch (true) {
146
            case array_key_exists($name, $_ENV):
147
                return true;
148
            case array_key_exists($name, $_SERVER):
149
                return true;
150
            default:
151
                $value = getenv($name);
152
                return $value !== false;
153
        }
154
    }
155
156
    /**
157
     * Returns config section specific for current factory. Returns an empty array for invalid section name in case of
158
     * improper method call
159
     *
160
     * @return array
161
     */
162
    protected function getFactorySpecificConfig(): array
163
    {
164
        return self::$config[$this->configSectionName] ?: [];
165
    }
166
167
    /**
168
     * Performs basic validation of config structure. This method is to be overridden by custom implementations if
169
     * required
170
     *
171
     * @throws InvalidConfigException
172
     * @throws InvalidApiClassException
173
     */
174
    protected function assertConfigIsValid(): void
175
    {
176
        foreach ($this->configProperties as $property) {
177
            if (empty(self::$config[$this->configSectionName][$property])) {
178
                throw new InvalidConfigException(
179
                    sprintf(
180
                        "Config section does not exist or is not filled properly: %s (\"%s\" is missing)",
181
                        $this->configSectionName,
182
                        $property
183
                    )
184
                );
185
            }
186
        }
187
188
        $this->assertApiClassImplementsInterface($this->apiInterface);
189
    }
190
191
    /**
192
     * @param string $interface
193
     *
194
     * @throws InvalidApiClassException
195
     */
196
    protected function assertApiClassImplementsInterface(string $interface): void
197
    {
198
        $apiClass = $this->getFactorySpecificConfig()['default'];
199
200
        if (false === ($interfaces = @class_implements($apiClass, true))) {
201
            throw new InvalidApiClassException(
202
                sprintf("Class %s is invalid", $apiClass)
203
            );
204
        }
205
206
        if (!in_array($interface, $interfaces)) {
207
            throw new InvalidApiClassException(
208
                sprintf("Class %s must implement %s interface", $apiClass, $interface)
209
            );
210
        }
211
    }
212
213
    /**
214
     * Initialize environment variables
215
     *
216
     * @throws InvalidPathException
217
     */
218
    protected function initEnvironment(): void
219
    {
220
        $envFile = $this->getRootRelatedPath($this->getEnvFileName());
221
222
        $this->assertFileIsReadable($envFile);
223
224
        self::$envIsSet = (bool)(new Dotenv($this->getRootDirectory(), $this->getEnvFileName()))->load();
0 ignored issues
show
Documentation Bug introduced by
It seems like (bool)new Dotenv\Dotenv(...tEnvFileName())->load() of type boolean is incompatible with the declared type null|array of property $envIsSet.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
225
    }
226
227
    /**
228
     * Initialize config variables
229
     *
230
     * @throws InvalidPathException
231
     */
232
    protected function initConfig(): void
233
    {
234
        $configFile = $this->getRootRelatedPath($this->getConfigFileName());
235
236
        $this->assertFileIsReadable($configFile);
237
238
        self::$config = (array)(include $configFile);
239
    }
240
241
    /**
242
     * @param string $filePath
243
     *
244
     * @return string
245
     */
246
    protected function getRootRelatedPath(string $filePath): string
247
    {
248
        return $this->getRootDirectory() . DIRECTORY_SEPARATOR . trim($filePath, DIRECTORY_SEPARATOR);
249
    }
250
251
    /**
252
     * Returns package root directory
253
     *
254
     * @return string
255
     *
256
     * @codeCoverageIgnore
257
     */
258
    protected function getRootDirectory(): string
259
    {
260
        return dirname(\GinoPane\PHPolyglot\ROOT_DIRECTORY);
261
    }
262
263
    /**
264
     * Returns environment file name
265
     *
266
     * @return string
267
     *
268
     * @codeCoverageIgnore
269
     */
270
    protected function getEnvFileName(): string
271
    {
272
        return self::ENV_FILE_NAME;
273
    }
274
275
    /**
276
     * Returns config file name
277
     *
278
     * @return string
279
     *
280
     * @codeCoverageIgnore
281
     */
282
    protected function getConfigFileName(): string
283
    {
284
        return self::CONFIG_FILE_NAME;
285
    }
286
287
    /**
288
     * A simple check that file exists and is readable
289
     *
290
     * @param string $fileName
291
     *
292
     * @throws InvalidPathException
293
     */
294
    protected function assertFileIsReadable(string $fileName): void
295
    {
296
        if (!is_file($fileName) || !is_readable($fileName)) {
297
            throw new InvalidPathException(sprintf('Unable to read the file at %s', $fileName));
298
        }
299
    }
300
301
    /**
302
     * A simple check that file exists and is readable
303
     *
304
     * @param string $directoryName
305
     *
306
     * @throws InvalidPathException
307
     */
308
    protected function assertDirectoryIsWriteable(string $directoryName): void
309
    {
310
        if (!is_dir($directoryName) || !is_writable($directoryName)) {
311
            throw new InvalidPathException(sprintf('Unable to write to the directory at "%s"', $directoryName));
312
        }
313
    }
314
}
315