Completed
Push — 2.x ( c13c96...12d966 )
by Cy
01:59
created

Loader   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 25
lcom 1
cbo 4
dl 0
loc 288
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 1
A load() 0 7 1
A loadConfiguration() 0 12 3
A shouldOverrideLaravelRedisApi() 0 4 1
A shouldLoadConfiguration() 0 8 2
A configureLumenComponents() 0 6 1
A loadPackageConfiguration() 0 14 2
A setConfigurationFor() 0 10 3
A setSessionConfiguration() 0 11 4
A getPackageConfigurationFor() 0 8 2
A mergePackageConfiguration() 0 7 1
A normalizeHosts() 0 13 2
A cleanPackageConfiguration() 0 11 2
1
<?php
2
3
namespace Monospice\LaravelRedisSentinel\Configuration;
4
5
use Illuminate\Contracts\Container\Container;
6
use Illuminate\Foundation\Application as LaravelApplication;
7
use Illuminate\Support\Arr;
8
use Laravel\Lumen\Application as LumenApplication;
9
use Monospice\LaravelRedisSentinel\Configuration\HostNormalizer;
10
11
/**
12
 * The internal configuration loader for the package. Used by the package's
13
 * service provider.
14
 *
15
 * This package provides developers three ways to configure it: through the
16
 * environment, by adding config values to the configuration for the other
17
 * components that the package wraps, and by creating an external package
18
 * configuration file that overrides the default internal configuration.
19
 * The package uses its configuration information to set Redis connection,
20
 * cache, session, and queue configuration values when these are missing.
21
 * This approach simplifies the code needed to configure the package for many
22
 * applications while still providing the flexibility needed for advanced
23
 * setups. This class reconciles each of the configuration methods.
24
 *
25
 * The package's configuration contains partial elements from several other
26
 * component configurations. By default, the package removes its configuration
27
 * after merging the values into each of the appropriate config locations for
28
 * the components it initializes. This behavior prevents the artisan CLI's
29
 * "config:cache" command from saving unnecessary configuration values to the
30
 * configuration cache file. Set the value of "redis-sentinel.clean_config" to
31
 * FALSE to disable this behavior.
32
 *
33
 * To support these configuration scenarios, this class follows these rules:
34
 *
35
 *   - Values in application config files ("config/database.php", etc.) have
36
 *     the greatest precedence. The package will use these values before any
37
 *     others and will not modify these values if they exist.
38
 *   - The package will use values in a developer-supplied package config file
39
 *     located in the application's "config/" directory with the filename of
40
 *     "redis-sentinel.php" for any values not found in the application's
41
 *     standard configuration files before using it's default configuration.
42
 *   - For any configuration values not provided by standard application
43
 *     config files or a developer-supplied custom config file, the package
44
 *     uses it's internal default configuration that reads configuration values
45
 *     from environment variables.
46
 *   - The package will copy values from it's configuration to the standard
47
 *     application configuration at runtime if these are missing. For example,
48
 *     if the application configuration doesn't contain a key for "database.
49
 *     redis-sentinel" (the Redis Sentinel connections), this class will copy
50
 *     its values from "redis-sentinel.database.redis-sentinel" to "database.
51
 *     redis-sentinel".
52
 *   - After loading its configuration, the package must only use configuration
53
 *     values from the standard application config locations. For example, the
54
 *     package will read the values from "database.redis-sentinel" to configure
55
 *     Redis Sentinel connections, not "redis-sentinel.database.redis-sentinel".
56
 *
57
 * @category Package
58
 * @package  Monospice\LaravelRedisSentinel
59
 * @author   Cy Rossignol <[email protected]>
60
 * @license  See LICENSE file
61
 * @link     https://github.com/monospice/laravel-redis-sentinel-drivers
62
 */
63
class Loader
64
{
65
    /**
66
     * The path to the package's default configuration file.
67
     *
68
     * @var string
69
     */
70
    const CONFIG_PATH = __DIR__ . '/../../config/redis-sentinel.php';
71
72
    /**
73
     * Indicates whether the current application runs the Lumen framework.
74
     *
75
     * @var bool
76
     */
77
    public $isLumen;
78
79
    /**
80
     * Indicates whether the current application supports sessions.
81
     *
82
     * @var bool
83
     */
84
    public $supportsSessions;
85
86
    /**
87
     * The current application instance that provides context and services
88
     * used to load the appropriate configuration.
89
     *
90
     * @var LaravelApplication|LumenApplication
91
     */
92
    private $app;
93
94
    /**
95
     * Used to fetch and set application configuration values.
96
     *
97
     * @var \Illuminate\Contracts\Config\Repository
98
     */
99
    private $config;
100
101
    /**
102
     * Contains the set of configuration values used to configure the package
103
     * as loaded from "config/redis-sentinel.php". Empty when the application's
104
     * standard config files provide all the values needed to configure the
105
     * package (such as when a developer provides a custom config).
106
     *
107
     * @var array
108
     */
109
    private $packageConfig;
110
111
    /**
112
     * Initialize the configuration loader. Any actual loading occurs when
113
     * calling the 'loadConfiguration()' method.
114
     *
115
     * @param LaravelApplication|LumenApplication $app The current application
0 ignored issues
show
Documentation introduced by
Should the type for parameter $app not be Container?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
116
     * instance that provides context and services needed to load the
117
     * appropriate configuration.
118
     */
119
    public function __construct(Container $app)
120
    {
121
        $this->app = $app;
0 ignored issues
show
Documentation Bug introduced by
It seems like $app of type object<Illuminate\Contracts\Container\Container> is incompatible with the declared type object<Illuminate\Founda...avel\Lumen\Application> of property $app.

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...
122
        $this->config = $app->make('config');
123
124
        $lumenApplicationClass = 'Laravel\Lumen\Application';
125
126
        $this->isLumen = $app instanceof $lumenApplicationClass;
127
        $this->supportsSessions = $app->bound('session');
128
    }
129
130
    /**
131
     * Create an instance of the loader and load the configuration in one step.
132
     *
133
     * @param LaravelApplication|LumenApplication $app The current application
0 ignored issues
show
Documentation introduced by
Should the type for parameter $app not be Container?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
134
     * instance that provides context and services needed to load the
135
     * appropriate configuration.
136
     *
137
     * @return self An initialized instance of this class
138
     */
139
    public static function load(Container $app)
140
    {
141
        $loader = new self($app);
142
        $loader->loadConfiguration();
143
144
        return $loader;
145
    }
146
147
    /**
148
     * Load the package configuration.
149
     *
150
     * @return void
151
     */
152
    public function loadConfiguration()
153
    {
154
        if (! $this->shouldLoadConfiguration()) {
155
            return;
156
        }
157
158
        if ($this->isLumen) {
159
            $this->configureLumenComponents();
160
        }
161
162
        $this->loadPackageConfiguration();
163
    }
164
165
    /**
166
     * Determine whether the package should override Laravel's standard Redis
167
     * API ("Redis" facade and "redis" service binding).
168
     *
169
     * @return bool TRUE if the package should override Laravel's standard
170
     * Redis API
171
     */
172
    public function shouldOverrideLaravelRedisApi()
173
    {
174
        return $this->config->get('database.redis.driver') === 'sentinel';
175
    }
176
177
    /**
178
     * Determine if the package should automatically configure itself.
179
     *
180
     * Developers may set the value of "redis-sentinel.load_config" to FALSE to
181
     * disable the package's automatic configuration. This class also sets this
182
     * value to FALSE after loading the package configuration to skip the auto-
183
     * configuration when the application cached its configuration values (via
184
     * "artisan config:cache", for example).
185
     *
186
     * @return bool TRUE if the package should load its configuration
187
     */
188
    protected function shouldLoadConfiguration()
189
    {
190
        if ($this->isLumen) {
191
            $this->app->configure('redis-sentinel');
1 ignored issue
show
Bug introduced by
The method configure does only exist in Laravel\Lumen\Application, but not in Illuminate\Foundation\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
192
        }
193
194
        return $this->config->get('redis-sentinel.load_config', true) === true;
195
    }
196
197
    /**
198
     * Configure the Lumen components that this package depends on.
199
     *
200
     * Lumen lazily loads many of its components. We must instruct Lumen to
201
     * load the configuration for components that this class configures so
202
     * that the values are accessible and so that the framework does not
203
     * revert the configuration settings that this class changes when one of
204
     * the components initializes later.
205
     *
206
     * @return void
207
     */
208
    protected function configureLumenComponents()
209
    {
210
        $this->app->configure('database');
1 ignored issue
show
Bug introduced by
The method configure does only exist in Laravel\Lumen\Application, but not in Illuminate\Foundation\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
211
        $this->app->configure('cache');
212
        $this->app->configure('queue');
213
    }
214
215
    /**
216
     * Reconcile the package configuration and use it to set the appropriate
217
     * configuration values for other application components.
218
     *
219
     * @return void
220
     */
221
    protected function loadPackageConfiguration()
222
    {
223
        $this->setConfigurationFor('database.redis-sentinel');
224
        $this->setConfigurationFor('database.redis.driver');
225
        $this->setConfigurationFor('cache.stores.redis-sentinel');
226
        $this->setConfigurationFor('queue.connections.redis-sentinel');
227
        $this->setSessionConfiguration();
228
229
        $this->normalizeHosts();
230
231
        if ($this->packageConfig !== null) {
232
            $this->cleanPackageConfiguration();
233
        }
234
    }
235
236
    /**
237
     * Set the application configuration value for the specified key with the
238
     * value from the package configuration.
239
     *
240
     * @param string $configKey   The key of the config value to set. Should
241
     * correspond to a key in the package's configuration.
242
     * @param bool   $checkExists If TRUE, don't set the value if the key
243
     * already exists in the application configuration.
244
     *
245
     * @return void
246
     */
247
    protected function setConfigurationFor($configKey, $checkExists = true)
248
    {
249
        if ($checkExists && $this->config->has($configKey)) {
250
            return;
251
        }
252
253
        $config = $this->getPackageConfigurationFor($configKey);
254
255
        $this->config->set($configKey, $config);
256
    }
257
258
    /**
259
     * Set the application session configuration as specified by the package's
260
     * configuration if the app supports sessions.
261
     *
262
     * @return void
263
     */
264
    protected function setSessionConfiguration()
265
    {
266
        if (! $this->supportsSessions
267
            || $this->config->get('session.driver') !== 'redis-sentinel'
268
            || $this->config->get('session.connection') !== null
269
        ) {
270
            return;
271
        }
272
273
        $this->setConfigurationFor('session.connection', false);
274
    }
275
276
    /**
277
     * Get the package configuration for the specified key.
278
     *
279
     * @param string $configKey The key of the configuration value to get
280
     *
281
     * @return mixed The value of the configuration with the specified key
282
     */
283
    protected function getPackageConfigurationFor($configKey)
284
    {
285
        if ($this->packageConfig === null) {
286
            $this->mergePackageConfiguration();
287
        }
288
289
        return Arr::get($this->packageConfig, $configKey);
290
    }
291
292
    /**
293
     * Merge the package's default configuration with the override config file
294
     * supplied by the developer, if any.
295
     *
296
     * @return void
297
     */
298
    protected function mergePackageConfiguration()
299
    {
300
        $defaultConfig = require self::CONFIG_PATH;
301
        $currentConfig = $this->config->get('redis-sentinel', [ ]);
302
303
        $this->packageConfig = array_merge($defaultConfig, $currentConfig);
304
    }
305
306
    /**
307
     * Parse Redis Sentinel connection host definitions to create single host
308
     * entries for host definitions that specify multiple hosts.
309
     *
310
     * @return void
311
     */
312
    protected function normalizeHosts()
313
    {
314
        $connections = $this->config->get('database.redis-sentinel');
315
316
        if (! is_array($connections)) {
317
            return;
318
        }
319
320
        $this->config->set(
321
            'database.redis-sentinel',
322
            HostNormalizer::normalizeConnections($connections)
323
        );
324
    }
325
326
    /**
327
     * Remove the package's configuration from the application configuration
328
     * repository.
329
     *
330
     * This package's configuration contains partial elements from several
331
     * other component configurations. By default, the package removes its
332
     * configuration after merging the values into each of the appropriate
333
     * config locations for the components it initializes. This behavior
334
     * prevents the artisan "config:cache" command from saving unnecessary
335
     * configuration values to the cache file.
336
     *
337
     * @return void
338
     */
339
    protected function cleanPackageConfiguration()
340
    {
341
        if ($this->config->get('redis-sentinel.clean_config', true) !== true) {
342
            return;
343
        }
344
345
        $this->config->set('redis-sentinel', [
346
            'Config merged. Set "redis-sentinel.clean_config" = false to keep.',
347
            'load_config' => false, // skip loading package config when cached
348
        ]);
349
    }
350
}
351