Passed
Push — master ( beb49a...b157da )
by Gino
02:22
created

ApiFactoryAbstract::setEnvironmentVariable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 2
dl 0
loc 12
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 says
43
     *
44
     * @var array|null
45
     */
46
    protected static $env = null;
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 (is_null(self::$env)) {
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
     * @param array $env
108
     */
109
    public static function setEnv(array $env = [])
110
    {
111
        self::$env = 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 $env.

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

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