Completed
Push — 2.x ( 341981...b9f40f )
by Cy
01:39
created

Loader::set()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
1
<?php
2
3
namespace Monospice\LaravelRedisSentinel\Configuration;
4
5
use Illuminate\Foundation\Application as LaravelApplication;
6
use Illuminate\Support\Arr;
7
use Laravel\Lumen\Application as LumenApplication;
8
use Monospice\LaravelRedisSentinel\Configuration\HostNormalizer;
9
use Monospice\LaravelRedisSentinel\Manager;
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
     * Indicates whether the package should override Laravel's standard Redis
88
     * API ("Redis" facade and "redis" service binding).
89
     *
90
     * @var bool
91
     */
92
    public $shouldOverrideLaravelRedisApi;
93
94
    /**
95
     * The current Laravel or Lumen application instance that provides context
96
     * and services used to load the appropriate configuration.
97
     *
98
     * @var LaravelApplication|LumenApplication
99
     */
100
    private $app;
101
102
    /**
103
     * Used to fetch and set application configuration values.
104
     *
105
     * @var \Illuminate\Contracts\Config\Repository
106
     */
107
    private $config;
108
109
    /**
110
     * Contains the set of configuration values used to configure the package
111
     * as loaded from "config/redis-sentinel.php". Empty when the application's
112
     * standard config files provide all the values needed to configure the
113
     * package (such as when a developer provides a custom config).
114
     *
115
     * @var array
116
     */
117
    private $packageConfig;
118
119
    /**
120
     * Initialize the configuration loader. Any actual loading occurs when
121
     * calling the 'loadConfiguration()' method.
122
     *
123
     * @param LaravelApplication|LumenApplication $app The current application
124
     * instance that provides context and services needed to load the
125
     * appropriate configuration.
126
     */
127
    public function __construct($app)
128
    {
129
        $this->app = $app;
130
        $this->config = $app->make('config');
131
132
        $lumenApplicationClass = 'Laravel\Lumen\Application';
133
134
        $this->isLumen = $app instanceof $lumenApplicationClass;
135
        $this->supportsSessions = $app->bound('session');
136
    }
137
138
    /**
139
     * Create an instance of the loader and load the configuration in one step.
140
     *
141
     * @param LaravelApplication|LumenApplication $app The current application
142
     * instance that provides context and services needed to load the
143
     * appropriate configuration.
144
     *
145
     * @return self An initialized instance of this class
146
     */
147
    public static function load($app)
148
    {
149
        $loader = new self($app);
150
        $loader->loadConfiguration();
151
152
        return $loader;
153
    }
154
155
    /**
156
     * Load the package configuration.
157
     *
158
     * @return void
159
     */
160
    public function loadConfiguration()
161
    {
162
        if ($this->shouldLoadConfiguration()) {
163
            if ($this->isLumen) {
164
                $this->configureLumenComponents();
165
            }
166
167
            $this->loadPackageConfiguration();
168
        }
169
170
        // Previous versions of the package looked for the value 'sentinel':
171
        $redisDriver = $this->config->get('database.redis.driver');
172
        $this->shouldOverrideLaravelRedisApi = $redisDriver === 'redis-sentinel'
173
            || $redisDriver === 'sentinel';
174
    }
175
176
    /**
177
     * Get the fully-qualified class name of the RedisSentinelManager class
178
     * for the current version of Laravel or Lumen.
179
     *
180
     * @return string The class name of the appropriate RedisSentinelManager
181
     * with its namespace
182
     */
183
    public function getVersionedRedisSentinelManagerClass()
184
    {
185
        if ($this->isLumen) {
186
            $appVersion = substr($this->app->version(), 7, 3); // ex. "5.4"
187
            $frameworkVersion = '5.4';
188
        } else {
189
            $appVersion = \Illuminate\Foundation\Application::VERSION;
190
            $frameworkVersion = '5.4.20';
191
        }
192
193
        if (version_compare($appVersion, $frameworkVersion, 'lt')) {
194
            return Manager\Laravel540RedisSentinelManager::class;
195
        }
196
197
        return Manager\Laravel5420RedisSentinelManager::class;
198
    }
199
200
    /**
201
     * Fetch the specified application configuration value.
202
     *
203
     * This helper method enables the package's service providers to get config
204
     * values without having to resolve the config service from the container.
205
     *
206
     * @param string|array $key     The key(s) for the value(s) to fetch.
207
     * @param mixed        $default Returned if the key does not exist.
208
     *
209
     * @return mixed The requested configuration value or the provided default
210
     * if the key does not exist.
211
     */
212
    public function get($key, $default = null)
213
    {
214
        return $this->config->get($key, $default);
215
    }
216
217
    /**
218
     * Set the specified application configuration value.
219
     *
220
     * This helper method enables the package's service providers to set config
221
     * values without having to resolve the config service from the container.
222
     *
223
     * @param string|array $key   The key of the value or a tree of values as
224
     * an associative array.
225
     * @param mixed        $value The value to set for the specified key.
226
     *
227
     * @return void
228
     */
229
    public function set($key, $value = null)
230
    {
231
        $this->config->set($key, $value);
232
    }
233
234
    /**
235
     * Determine if the package should automatically configure itself.
236
     *
237
     * Developers may set the value of "redis-sentinel.load_config" to FALSE to
238
     * disable the package's automatic configuration. This class also sets this
239
     * value to FALSE after loading the package configuration to skip the auto-
240
     * configuration when the application cached its configuration values (via
241
     * "artisan config:cache", for example).
242
     *
243
     * @return bool TRUE if the package should load its configuration
244
     */
245
    protected function shouldLoadConfiguration()
246
    {
247
        if ($this->isLumen) {
248
            $this->app->configure('redis-sentinel');
249
        }
250
251
        return $this->config->get('redis-sentinel.load_config', true) === true;
252
    }
253
254
    /**
255
     * Configure the Lumen components that this package depends on.
256
     *
257
     * Lumen lazily loads many of its components. We must instruct Lumen to
258
     * load the configuration for components that this class configures so
259
     * that the values are accessible and so that the framework does not
260
     * revert the configuration settings that this class changes when one of
261
     * the components initializes later.
262
     *
263
     * @return void
264
     */
265
    protected function configureLumenComponents()
266
    {
267
        $this->app->configure('database');
268
        $this->app->configure('broadcasting');
269
        $this->app->configure('cache');
270
        $this->app->configure('queue');
271
    }
272
273
    /**
274
     * Reconcile the package configuration and use it to set the appropriate
275
     * configuration values for other application components.
276
     *
277
     * @return void
278
     */
279
    protected function loadPackageConfiguration()
280
    {
281
        $this->setConfigurationFor('database.redis-sentinel');
282
        $this->setConfigurationFor('database.redis.driver');
283
        $this->setConfigurationFor('broadcasting.connections.redis-sentinel');
284
        $this->setConfigurationFor('cache.stores.redis-sentinel');
285
        $this->setConfigurationFor('queue.connections.redis-sentinel');
286
        $this->setSessionConfiguration();
287
288
        $this->normalizeHosts();
289
290
        if ($this->packageConfig !== null) {
291
            $this->cleanPackageConfiguration();
292
        }
293
    }
294
295
    /**
296
     * Set the application configuration value for the specified key with the
297
     * value from the package configuration.
298
     *
299
     * @param string $configKey   The key of the config value to set. Should
300
     * correspond to a key in the package's configuration.
301
     * @param bool   $checkExists If TRUE, don't set the value if the key
302
     * already exists in the application configuration.
303
     *
304
     * @return void
305
     */
306
    protected function setConfigurationFor($configKey, $checkExists = true)
307
    {
308
        if ($checkExists && $this->config->has($configKey)) {
309
            return;
310
        }
311
312
        $config = $this->getPackageConfigurationFor($configKey);
313
314
        $this->config->set($configKey, $config);
315
    }
316
317
    /**
318
     * Set the application session configuration as specified by the package's
319
     * configuration if the app supports sessions.
320
     *
321
     * @return void
322
     */
323
    protected function setSessionConfiguration()
324
    {
325
        if (! $this->supportsSessions
326
            || $this->config->get('session.driver') !== 'redis-sentinel'
327
            || $this->config->get('session.connection') !== null
328
        ) {
329
            return;
330
        }
331
332
        $this->setConfigurationFor('session.connection', false);
333
    }
334
335
    /**
336
     * Get the package configuration for the specified key.
337
     *
338
     * @param string $configKey The key of the configuration value to get
339
     *
340
     * @return mixed The value of the configuration with the specified key
341
     */
342
    protected function getPackageConfigurationFor($configKey)
343
    {
344
        if ($this->packageConfig === null) {
345
            $this->mergePackageConfiguration();
346
        }
347
348
        return Arr::get($this->packageConfig, $configKey);
349
    }
350
351
    /**
352
     * Merge the package's default configuration with the override config file
353
     * supplied by the developer, if any.
354
     *
355
     * @return void
356
     */
357
    protected function mergePackageConfiguration()
358
    {
359
        $defaultConfig = require self::CONFIG_PATH;
360
        $currentConfig = $this->config->get('redis-sentinel', [ ]);
361
362
        $this->packageConfig = array_merge($defaultConfig, $currentConfig);
363
    }
364
365
    /**
366
     * Parse Redis Sentinel connection host definitions to create single host
367
     * entries for host definitions that specify multiple hosts.
368
     *
369
     * @return void
370
     */
371
    protected function normalizeHosts()
372
    {
373
        $connections = $this->config->get('database.redis-sentinel');
374
375
        if (! is_array($connections)) {
376
            return;
377
        }
378
379
        $this->config->set(
380
            'database.redis-sentinel',
381
            HostNormalizer::normalizeConnections($connections)
382
        );
383
    }
384
385
    /**
386
     * Remove the package's configuration from the application configuration
387
     * repository.
388
     *
389
     * This package's configuration contains partial elements from several
390
     * other component configurations. By default, the package removes its
391
     * configuration after merging the values into each of the appropriate
392
     * config locations for the components it initializes. This behavior
393
     * prevents the artisan "config:cache" command from saving unnecessary
394
     * configuration values to the cache file.
395
     *
396
     * @return void
397
     */
398
    protected function cleanPackageConfiguration()
399
    {
400
        // When we're finished with the internal package configuration, break
401
        // the reference so that it can be garbage-collected:
402
        $this->packageConfig = null;
403
404
        if ($this->config->get('redis-sentinel.clean_config', true) !== true) {
405
            return;
406
        }
407
408
        $this->config->set('redis-sentinel', [
409
            'Config merged. Set "redis-sentinel.clean_config" = false to keep.',
410
            'load_config' => false, // skip loading package config when cached
411
        ]);
412
    }
413
}
414