Passed
Branch master (a02de7)
by Mihail
02:35
created

Config::fromJsonFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 2
c 2
b 0
f 0
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 */
11
12
namespace Koded\Stdlib;
13
14
use Exception;
15
use function call_user_func;
16
use function class_exists;
17
use function current;
18
use function error_get_last;
19
use function error_log;
20
use function file_get_contents;
21
use function getcwd;
22
use function getenv;
23
use function is_string;
24
use function iterator_to_array;
25
use function join;
26
use function json_decode;
27
use function parse_ini_file;
28
use function parse_ini_string;
29
use function pathinfo;
30
use function strtr;
31
use function ucfirst;
32
33
/**
34
 * Class Config works as a parameter bag that provides ways to fill it
35
 * from files or other Config instances. There are 2 common patterns
36
 * to populate the config,
37
 *
38
 * either you can fill the Config instance from config files:
39
 *
40
 *     $app->config()->fromPhpFile('myconfig.php');
41
 *     $app->config()->fromJsonFile('myconfig.json');
42
 *     $app->config()->fromEnvFile('.env');
43
 *     $app->config()->fromIniFile('myconfig.ini');
44
 *
45
 * or alternatively you can define the configuration options in the instance
46
 * that calls `fromObject`,
47
 *
48
 *     $app->config()->fromObject(MyConfig::class);
49
 *     $app->config()->fromObject($myconfig); // $myconfig is instance of Config
50
 *
51
 * Other interesting way to load configuration is from an environment variable
52
 * that points to a file
53
 *
54
 *     $app->config()->fromEnvVariable('MY_APP_SETTINGS');
55
 *
56
 * In this case, before launching the application you have to set the env variable
57
 * to the file you want to use. On Linux and OSX use the export statement
58
 *
59
 *     export MY_APP_SETTINGS='/path/to/config/file.php'
60
 *
61
 * or somewhere in your app bootstrap phase before constructing the Api instance
62
 *
63
 *     putenv('MY_APP_SETTINGS=/path/to/config/file.php');
64
 *
65
 */
66
class Config extends Arguments implements Configuration
67
{
68
    public string $rootPath = '';
69
    private bool $silent = false;
70
71
    /**
72
     * Config constructor.
73
     *
74
     * @param string $rootPath Path to which files are read relative from.
75
     *                                  When the config object is created by an application/library
76
     *                                  this is the application's root path
77
     * @param Data|null $defaults [optional] An Optional config object with default values
78
     */
79 25
    public function __construct(string $rootPath = '', Data $defaults = null)
80
    {
81 25
        parent::__construct($defaults ? $defaults->toArray() : []);
82 25
        if (!$this->rootPath = $rootPath) {
83 24
            $this->rootPath = getcwd();
84
        }
85 25
    }
86
87
    /**
88
     * Bad method calls can be suppressed and allow the app
89
     * to continue execution by setting the silent(true).
90
     *
91
     * The app should handle their configuration appropriately.
92
     *
93
     * @param string $name Method name
94
     * @param array|null $arguments [optional]
95
     * @return Configuration
96
     * @throws Exception
97
     */
98 3
    public function __call(string $name, array|null $arguments): Configuration
99
    {
100 3
        if (false === $this->silent) {
101 2
            throw new Exception('Unable to load the configuration file ' . current($arguments));
102
        }
103 1
        return $this;
104
    }
105
106 1
    public function build(string $context): Configuration
107
    {
108 1
        throw new Exception('Configuration factory should implement the method ' . __METHOD__);
109
    }
110
111 1
    public function withParameters(array $parameters): Configuration
112
    {
113 1
        return $this->import($parameters);
114
    }
115
116 2
    public function fromObject(object|string $object): Configuration
117
    {
118 2
        if (is_string($object) && class_exists($object)) {
119 1
            $object = new $object;
120
        }
121 2
        $this->rootPath = $object->rootPath ?: $this->rootPath;
122 2
        return $this->import(iterator_to_array($object));
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type string; however, parameter $iterator of iterator_to_array() does only seem to accept Traversable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

122
        return $this->import(iterator_to_array(/** @scrutinizer ignore-type */ $object));
Loading history...
123
    }
124
125 2
    public function fromJsonFile(string $filename): Configuration
126
    {
127 2
        return $this->loadDataFrom($filename,
128 2
            fn() => json_decode(file_get_contents($filename), true)
129 2
        );
130
    }
131
132 3
    public function fromIniFile(string $filename): Configuration
133
    {
134 3
        return $this->loadDataFrom($filename,
135 3
            fn() => parse_ini_file($filename, true, INI_SCANNER_TYPED) ?: []
136 3
        );
137
    }
138
139 4
    public function fromEnvFile(string $filename, string $namespace = ''): Configuration
140
    {
141
        try {
142 4
            $data = parse_ini_file($this->filename($filename), true, INI_SCANNER_TYPED) ?: [];
143 3
            env('', null, $this->filter($data, $namespace, false));
144 1
        } catch (Exception $e) {
145 1
            error_log('[Configuration error]: ' . $e->getMessage());
146 1
            env('', null, []);
147
        } finally {
148 4
            return $this;
149
        }
150
    }
151
152 5
    public function fromEnvVariable(string $variable): Configuration
153
    {
154 5
        if (false === empty($filename = getenv($variable))) {
155 3
            $extension = ucfirst(pathinfo($filename, PATHINFO_EXTENSION));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($filename, Kode...lib\PATHINFO_EXTENSION) can also be of type array; however, parameter $string of ucfirst() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

155
            $extension = ucfirst(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION));
Loading history...
156 3
            return call_user_func([$this, "from{$extension}File"], $filename);
157
        }
158 2
        if (false === $this->silent) {
159 1
            throw new Exception(strtr('The environment variable ":variable" is not set
160
            and as such configuration could not be loaded. Set this variable and
161 1
            make it point to a configuration file', [':variable' => $variable]));
162
        }
163 1
        error_log('[Configuration error]: ' . (error_get_last()['message'] ?? "env var: ${variable}"));
164 1
        return $this;
165
    }
166
167 4
    public function fromPhpFile(string $filename): Configuration
168
    {
169 4
        return $this->loadDataFrom($filename, fn() => include $filename);
170
    }
171
172 5
    public function fromEnvironment(
173
        array  $variableNames,
174
        string $namespace = '',
175
        bool   $lowercase = true,
176
        bool   $trim = true): Configuration
177
    {
178 5
        $data = [];
179 5
        foreach ($variableNames as $variable) {
180 5
            $value = getenv($variable);
181 5
            $data[] = $variable . '=' . (false === $value ? 'null' : $value);
182
        }
183 5
        $data = parse_ini_string(join(PHP_EOL, $data), true, INI_SCANNER_TYPED) ?: [];
184 5
        $this->import($this->filter($data, $namespace, $lowercase, $trim));
185 5
        return $this;
186
    }
187
188 2
    public function silent(bool $silent): Configuration
189
    {
190 2
        $this->silent = $silent;
191 2
        return $this;
192
    }
193
194 1
    public function namespace(
195
        string $prefix,
196
        bool   $lowercase = true,
197
        bool   $trim = true): static
198
    {
199 1
        return (new static($this->rootPath))
200 1
            ->import($this->filter($this->toArray(), $prefix, $lowercase, $trim));
201
    }
202
203 9
    protected function loadDataFrom(string $filename, callable $loader): Configuration
204
    {
205 9
        return $this->import($loader($this->filename($filename)));
206
    }
207
208 13
    private function filename(string $filename): string
209
    {
210 13
        return ('/' !== $filename[0])
211 5
            ? $this->rootPath . '/' . $filename
212 13
            : $filename;
213
    }
214
}
215