Completed
Push — 1.x ( d9ab4d...3295a0 )
by Cy
08:55
created

Loader::setSessionConfiguration()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.2
c 0
b 0
f 0
cc 4
eloc 6
nc 2
nop 0
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
10
/**
11
 * The internal configuration loader for the package. Used by the package's
12
 * service provider.
13
 *
14
 * This package provides developers three ways to configure it: through the
15
 * environment, by adding config values to the configuration for the other
16
 * components that the package wraps, and by creating an external package
17
 * configuration file that overrides the default internal configuration.
18
 * The package uses its configuration information to set Redis connection,
19
 * cache, session, and queue configuration values when these are missing.
20
 * This approach simplifies the code needed to configure the package for many
21
 * applications while still providing the flexibility needed for advanced
22
 * setups. This class reconciles each of the configuration methods.
23
 *
24
 * The package's configuration contains partial elements from several other
25
 * component configurations. By default, the package removes its configuration
26
 * after merging the values into each of the appropriate config locations for
27
 * the components it initializes. This behavior prevents the artisan CLI's
28
 * "config:cache" command from saving unnecessary configuration values to the
29
 * configuration cache file. Set the value of "redis-sentinel.clean_config" to
30
 * FALSE to disable this behavior.
31
 *
32
 * To support these configuration scenarios, this class follows these rules:
33
 *
34
 *   - Values in application config files ("config/database.php", etc.) have
35
 *     the greatest precedence. The package will use these values before any
36
 *     others and will not modify these values if they exist.
37
 *   - The package will use values in a developer-supplied package config file
38
 *     located in the application's "config/" directory with the filename of
39
 *     "redis-sentinel.php" for any values not found in the application's
40
 *     standard configuration files before using it's default configuration.
41
 *   - For any configuration values not provided by standard application
42
 *     config files or a developer-supplied custom config file, the package
43
 *     uses it's internal default configuration that reads configuration values
44
 *     from environment variables.
45
 *   - The package will copy values from it's configuration to the standard
46
 *     application configuration at runtime if these are missing. For example,
47
 *     if the application configuration doesn't contain a key for "database.
48
 *     redis-sentinel" (the Redis Sentinel connections), this class will copy
49
 *     its values from "redis-sentinel.database.redis-sentinel" to "database.
50
 *     redis-sentinel".
51
 *   - After loading its configuration, the package must only use configuration
52
 *     values from the standard application config locations. For example, the
53
 *     package will read the values from "database.redis-sentinel" to configure
54
 *     Redis Sentinel connections, not "redis-sentinel.database.redis-sentinel".
55
 *
56
 * @category Package
57
 * @package  Monospice\LaravelRedisSentinel
58
 * @author   Cy Rossignol <[email protected]>
59
 * @license  See LICENSE file
60
 * @link     https://github.com/monospice/laravel-redis-sentinel-drivers
61
 */
62
class Loader
63
{
64
    /**
65
     * The path to the package's default configuration file.
66
     *
67
     * @var string
68
     */
69
    const CONFIG_PATH = __DIR__ . '/../../config/redis-sentinel.php';
70
71
    /**
72
     * Indicates whether the current application runs the Lumen framework.
73
     *
74
     * @var bool
75
     */
76
    public $isLumen;
77
78
    /**
79
     * Indicates whether the current application supports sessions.
80
     *
81
     * @var bool
82
     */
83
    public $supportsSessions;
84
85
    /**
86
     * The current Laravel or Lumen application instance that provides context
87
     * and services used to load the appropriate configuration.
88
     *
89
     * @var LaravelApplication|LumenApplication
90
     */
91
    private $app;
92
93
    /**
94
     * Used to fetch and set application configuration values.
95
     *
96
     * @var \Illuminate\Contracts\Config\Repository
97
     */
98
    private $config;
99
100
    /**
101
     * Contains the set of configuration values used to configure the package
102
     * as loaded from "config/redis-sentinel.php". Empty when the application's
103
     * standard config files provide all the values needed to configure the
104
     * package (such as when a developer provides a custom config).
105
     *
106
     * @var array
107
     */
108
    private $packageConfig;
109
110
    /**
111
     * Initialize the configuration loader. Any actual loading occurs when
112
     * calling the 'loadConfiguration()' method.
113
     *
114
     * @param LaravelApplication|LumenApplication $app The current application
115
     * instance that provides context and services needed to load the
116
     * appropriate configuration.
117
     */
118
    public function __construct($app)
119
    {
120
        $this->app = $app;
121
        $this->config = $app->make('config');
122
123
        $lumenApplicationClass = 'Laravel\Lumen\Application';
124
125
        $this->isLumen = $app instanceof $lumenApplicationClass;
126
        $this->supportsSessions = $app->bound('session');
127
    }
128
129
    /**
130
     * Create an instance of the loader and load the configuration in one step.
131
     *
132
     * @param LaravelApplication|LumenApplication $app The current application
133
     * instance that provides context and services needed to load the
134
     * appropriate configuration.
135
     *
136
     * @return self An initialized instance of this class
137
     */
138
    public static function load($app)
139
    {
140
        $loader = new self($app);
141
        $loader->loadConfiguration();
142
143
        return $loader;
144
    }
145
146
    /**
147
     * Load the package configuration.
148
     *
149
     * @return void
150
     */
151
    public function loadConfiguration()
152
    {
153
        if (! $this->shouldLoadConfiguration()) {
154
            return;
155
        }
156
157
        if ($this->isLumen) {
158
            $this->configureLumenComponents();
159
        }
160
161
        $this->loadPackageConfiguration();
162
    }
163
164
    /**
165
     * Determine whether the package should override Laravel's standard Redis
166
     * API ("Redis" facade and "redis" service binding).
167
     *
168
     * @return bool TRUE if the package should override Laravel's standard
169
     * Redis API
170
     */
171
    public function shouldOverrideLaravelRedisApi()
172
    {
173
        $redisDriver = $this->config->get('database.redis.driver');
174
175
        // Previous versions of the package looked for the value 'sentinel':
176
        return $redisDriver === 'redis-sentinel' || $redisDriver === 'sentinel';
177
    }
178
179
    /**
180
     * Determine if the package should automatically configure itself.
181
     *
182
     * Developers may set the value of "redis-sentinel.load_config" to FALSE to
183
     * disable the package's automatic configuration. This class also sets this
184
     * value to FALSE after loading the package configuration to skip the auto-
185
     * configuration when the application cached its configuration values (via
186
     * "artisan config:cache", for example).
187
     *
188
     * @return bool TRUE if the package should load its configuration
189
     */
190
    protected function shouldLoadConfiguration()
191
    {
192
        if ($this->isLumen) {
193
            $this->app->configure('redis-sentinel');
194
        }
195
196
        return $this->config->get('redis-sentinel.load_config', true) === true;
197
    }
198
199
    /**
200
     * Configure the Lumen components that this package depends on.
201
     *
202
     * Lumen lazily loads many of its components. We must instruct Lumen to
203
     * load the configuration for components that this class configures so
204
     * that the values are accessible and so that the framework does not
205
     * revert the configuration settings that this class changes when one of
206
     * the components initializes later.
207
     *
208
     * @return void
209
     */
210
    protected function configureLumenComponents()
211
    {
212
        $this->app->configure('database');
213
        $this->app->configure('cache');
214
        $this->app->configure('queue');
215
    }
216
217
    /**
218
     * Reconcile the package configuration and use it to set the appropriate
219
     * configuration values for other application components.
220
     *
221
     * @return void
222
     */
223
    protected function loadPackageConfiguration()
224
    {
225
        $this->setConfigurationFor('database.redis-sentinel');
226
        $this->setConfigurationFor('database.redis.driver');
227
        $this->setConfigurationFor('cache.stores.redis-sentinel');
228
        $this->setConfigurationFor('queue.connections.redis-sentinel');
229
        $this->setSessionConfiguration();
230
231
        $this->normalizeHosts();
232
233
        if ($this->packageConfig !== null) {
234
            $this->cleanPackageConfiguration();
235
        }
236
    }
237
238
    /**
239
     * Set the application configuration value for the specified key with the
240
     * value from the package configuration.
241
     *
242
     * @param string $configKey   The key of the config value to set. Should
243
     * correspond to a key in the package's configuration.
244
     * @param bool   $checkExists If TRUE, don't set the value if the key
245
     * already exists in the application configuration.
246
     *
247
     * @return void
248
     */
249
    protected function setConfigurationFor($configKey, $checkExists = true)
250
    {
251
        if ($checkExists && $this->config->has($configKey)) {
252
            return;
253
        }
254
255
        $config = $this->getPackageConfigurationFor($configKey);
256
257
        $this->config->set($configKey, $config);
258
    }
259
260
    /**
261
     * Set the application session configuration as specified by the package's
262
     * configuration if the app supports sessions.
263
     *
264
     * @return void
265
     */
266
    protected function setSessionConfiguration()
267
    {
268
        if (! $this->supportsSessions
269
            || $this->config->get('session.driver') !== 'redis-sentinel'
270
            || $this->config->get('session.connection') !== null
271
        ) {
272
            return;
273
        }
274
275
        $this->setConfigurationFor('session.connection', false);
276
    }
277
278
    /**
279
     * Get the package configuration for the specified key.
280
     *
281
     * @param string $configKey The key of the configuration value to get
282
     *
283
     * @return mixed The value of the configuration with the specified key
284
     */
285
    protected function getPackageConfigurationFor($configKey)
286
    {
287
        if ($this->packageConfig === null) {
288
            $this->mergePackageConfiguration();
289
        }
290
291
        return Arr::get($this->packageConfig, $configKey);
292
    }
293
294
    /**
295
     * Merge the package's default configuration with the override config file
296
     * supplied by the developer, if any.
297
     *
298
     * @return void
299
     */
300
    protected function mergePackageConfiguration()
301
    {
302
        $defaultConfig = require self::CONFIG_PATH;
303
        $currentConfig = $this->config->get('redis-sentinel', [ ]);
304
305
        $this->packageConfig = array_merge($defaultConfig, $currentConfig);
306
    }
307
308
    /**
309
     * Parse Redis Sentinel connection host definitions to create single host
310
     * entries for host definitions that specify multiple hosts.
311
     *
312
     * @return void
313
     */
314
    protected function normalizeHosts()
315
    {
316
        $connections = $this->config->get('database.redis-sentinel');
317
318
        if (! is_array($connections)) {
319
            return;
320
        }
321
322
        $this->config->set(
323
            'database.redis-sentinel',
324
            HostNormalizer::normalizeConnections($connections)
325
        );
326
    }
327
328
    /**
329
     * Remove the package's configuration from the application configuration
330
     * repository.
331
     *
332
     * This package's configuration contains partial elements from several
333
     * other component configurations. By default, the package removes its
334
     * configuration after merging the values into each of the appropriate
335
     * config locations for the components it initializes. This behavior
336
     * prevents the artisan "config:cache" command from saving unnecessary
337
     * configuration values to the cache file.
338
     *
339
     * @return void
340
     */
341
    protected function cleanPackageConfiguration()
342
    {
343
        if ($this->config->get('redis-sentinel.clean_config', true) !== true) {
344
            return;
345
        }
346
347
        $this->config->set('redis-sentinel', [
348
            'Config merged. Set "redis-sentinel.clean_config" = false to keep.',
349
            'load_config' => false, // skip loading package config when cached
350
        ]);
351
    }
352
}
353