Passed
Push — master ( 980804...08b834 )
by Mihail
09:48
created

Config   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 151
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 5
Bugs 0 Features 2
Metric Value
wmc 30
eloc 56
c 5
b 0
f 2
dl 0
loc 151
ccs 55
cts 55
cp 1
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A namespace() 0 7 1
A silent() 0 4 1
A fromJsonFile() 0 4 1
A fromPhpFile() 0 5 1
A loadDataFrom() 0 3 1
A __call() 0 6 2
A __construct() 0 5 3
A fromObject() 0 7 4
A fromIniFile() 0 4 2
A fromEnvVariable() 0 13 3
A fromEnvironment() 0 14 4
A withParameters() 0 3 1
A build() 0 3 1
A filename() 0 5 2
A fromEnvFile() 0 10 3
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
16
/**
17
 * Class Config works as a parameter bag that provides ways to fill it
18
 * from files or other Config instances. There are 2 common patterns
19
 * to populate the config,
20
 *
21
 * either you can fill the Config instance from config files:
22
 *
23
 *     $app->config()->fromPhpFile('myconfig.php');
24
 *     $app->config()->fromJsonFile('myconfig.json');
25
 *     $app->config()->fromEnvFile('.env');
26
 *     $app->config()->fromIniFile('myconfig.ini');
27
 *
28
 * or alternatively you can define the configuration options in the instance
29
 * that calls `fromObject`,
30
 *
31
 *     $app->config()->fromObject(MyConfig::class);
32
 *     $app->config()->fromObject($myconfig); // $myconfig is instance of Config
33
 *
34
 * Other interesting way to load configuration is from an environment variable
35
 * that points to a file
36
 *
37
 *     $app->config()->fromEnvVariable('MY_APP_SETTINGS');
38
 *
39
 * In this case, before launching the application you have to set the env variable
40
 * to the file you want to use. On Linux and OSX use the export statement
41
 *
42
 *     export MY_APP_SETTINGS='/path/to/config/file.php'
43
 *
44
 * or somewhere in your app bootstrap phase before constructing the Api instance
45
 *
46
 *     putenv('MY_APP_SETTINGS=/path/to/config/file.php');
47
 *
48
 */
49
class Config extends Arguments implements Configuration
50
{
51
    public string $rootPath = '';
52
    private bool $silent = false;
53
54
    /**
55
     * Config constructor.
56
     *
57
     * @param string    $rootPath       Path to which files are read relative from.
58
     *                                  When the config object is created by an application/library
59
     *                                  this is the application's root path
60
     * @param Data|null $defaults       [optional] An Optional config object with default values
61
     */
62
    public function __construct(string $rootPath = '', Data $defaults = null)
63
    {
64
        parent::__construct($defaults ? $defaults->toArray() : []);
65
        if (!$this->rootPath = $rootPath) {
66
            $this->rootPath = \getcwd();
67
        }
68 23
    }
69
70 23
    /**
71 23
     * Bad method calls can be suppressed and allow the app
72 23
     * to continue execution by setting the silent(true).
73
     *
74
     * The calling app should handle their configuration appropriately.
75
     *
76
     * @param string     $name      Method name
77
     * @param array|null $arguments [optional]
78
     *
79
     * @return Configuration
80
     * @throws Exception
81
     */
82
    public function __call(string $name, array|null $arguments): Configuration
0 ignored issues
show
Bug introduced by
The type Koded\Stdlib\null was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
83
    {
84
        if (false === $this->silent) {
85
            throw new Exception('Unable to load the configuration file ' . \current($arguments));
86 3
        }
87
        return $this;
88 3
    }
89 2
90
    public function build(string $context): Configuration
91
    {
92 1
        throw new Exception('Configuration factory should implement the method ' . __METHOD__);
93
    }
94
95 1
    public function withParameters(array $parameters): Configuration
96
    {
97 1
        return $this->import($parameters);
98
    }
99
100 1
    public function fromObject(object|string $object): Configuration
101
    {
102 1
        if (\is_string($object) && \class_exists($object)) {
103
            $object = new $object;
104
        }
105 2
        $this->rootPath = $object->rootPath ?: $this->rootPath;
106
        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

106
        return $this->import(\iterator_to_array(/** @scrutinizer ignore-type */ $object));
Loading history...
107 2
    }
108 1
109
    public function fromJsonFile(string $filename): Configuration
110
    {
111 2
        return $this->loadDataFrom($filename, function($filename) {
112
            return \json_decode(\file_get_contents($filename), true);
113 2
        });
114
    }
115
116 2
    public function fromIniFile(string $filename): Configuration
117
    {
118
        return $this->loadDataFrom($filename, function($filename) {
119
            return \parse_ini_file($filename, true, INI_SCANNER_TYPED) ?: [];
120 2
        });
121 2
    }
122
123
    public function fromEnvFile(string $filename, string $namespace = ''): Configuration
124 2
    {
125
        try {
126
            $data = \parse_ini_file($this->filename($filename), true, INI_SCANNER_TYPED) ?: [];
127 2
            env('', null, $this->filter($data, $namespace, false));
128 2
        } catch (Exception $e) {
129
            \error_log('[Configuration error]: ' . $e->getMessage());
130
            env('', null, []);
131 3
        } finally {
132
            return $this;
133
        }
134
    }
135 3
136 1
    public function fromEnvVariable(string $variable): Configuration
137 1
    {
138
        if (false === empty($filename = \getenv($variable))) {
139
            $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

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