Skip to content

Commit c5c27f2

Browse files
committed
Add redis connection option and config hints
1 parent b629742 commit c5c27f2

File tree

9 files changed

+211
-16
lines changed

9 files changed

+211
-16
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,9 @@ Checks that the database connection can be established, via the PDO, and that th
9090

9191
Checks that Redis connection can be established, and that the required config keys are set.
9292

93-
(No options)
93+
| Option | Description |
94+
| --- | --- |
95+
| `connection` | The name of the connection in `config/database.php` |)
9496

9597
### Configuration
9698

@@ -110,6 +112,20 @@ The accepted options for the `Configuration` preflight check is a list of config
110112
]
111113
```
112114

115+
You may also pass a hint (recommended for local development only) that will be shown if the key is not set. For example:
116+
117+
```php
118+
'local' => [
119+
Configuration::class => [
120+
'services.payment.key' => 'In dashboard as "Super Secret API Token Key Thing"',
121+
'services.mail.key',
122+
// ...
123+
]
124+
]
125+
```
126+
127+
When a new dev sets up their environment and is missing that config value, they will get that nice friendly message helping them find the key.
128+
113129
### Write Your Own!
114130

115131
If you have a special startup consideration you'd like to make, feel free write your own check, extending `Kirschbaum\PreflightChecks\Checks\PreflightCheck`.

src/Checks/Configuration.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,35 @@ class Configuration extends PreflightCheck
1111
*/
1212
public function check(Result $result): Result
1313
{
14-
$this->requiredConfig = $this->options;
14+
$this->loadRequiredConfigFromOptions();
1515

1616
return $this->checkConfig($result);
1717
}
18+
19+
/**
20+
* Parses the required config values from the options
21+
*/
22+
protected function loadRequiredConfigFromOptions()
23+
{
24+
foreach ($this->options as $key => $value) {
25+
if (is_numeric($key)) {
26+
// Just a list of config values, no help/description
27+
$this->requiredConfig[] = $value;
28+
29+
continue;
30+
}
31+
32+
// config value is key
33+
$this->requiredConfig[] = $key;
34+
35+
// Single entry is hint
36+
if (is_string($value)) {
37+
$this->configHints[$key] = $value;
38+
39+
continue;
40+
}
41+
42+
// Not a string, do nothing/reserve for future
43+
}
44+
}
1845
}

src/Checks/PreflightCheck.php

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ abstract class PreflightCheck
1111
*/
1212
protected array $requiredConfig = [];
1313

14+
/**
15+
* Configuration hints
16+
*/
17+
protected array $configHints = [];
18+
1419
/**
1520
* Is this a required step?
1621
*/
@@ -23,7 +28,7 @@ abstract class PreflightCheck
2328

2429
public function __construct(array $options = [])
2530
{
26-
$this->options = $options;
31+
$this->loadOptions($options);
2732
$this->boot();
2833
}
2934

@@ -65,6 +70,18 @@ protected function boot(): void
6570
{
6671
}
6772

73+
/**
74+
* Parses the options
75+
*/
76+
protected function loadOptions(array $options = [])
77+
{
78+
if (array_key_exists('config_hints', $options)) {
79+
$this->configHints = $options['config_hints'];
80+
unset($options['config_hints']);
81+
}
82+
$this->options = $options;
83+
}
84+
6885
/**
6986
* Initializes a result object.
7087
*/
@@ -112,7 +129,10 @@ protected function checkConfig(Result $result): Result
112129
}
113130

114131
if (! empty($missingKeys)) {
115-
return $result->fail('Missing configuration key(s).', $missingKeys);
132+
return $result->fail(
133+
$this->getConfigFailMessage($missingKeys),
134+
$this->getConfigFailKeyData($missingKeys),
135+
);
116136
}
117137

118138
return $result->pass($this->getConfigPassMessage(), $this->requiredConfig);
@@ -125,4 +145,25 @@ protected function getConfigPassMessage()
125145
{
126146
return 'Config keys are set!';
127147
}
148+
149+
/**
150+
* Gets the message for failed key check.
151+
*/
152+
protected function getConfigFailMessage(array $missingKeys = [])
153+
{
154+
return 'Missing configuration key(s).';
155+
}
156+
157+
/**
158+
* Gets the key data for missing keys.
159+
*/
160+
protected function getConfigFailKeyData(array $missingKeys = [])
161+
{
162+
return array_map(function ($key) {
163+
return array_filter([
164+
'key' => $key,
165+
'hint' => $this->configHints[$key] ?? null,
166+
]);
167+
}, $missingKeys);
168+
}
128169
}

src/Checks/Redis.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
class Redis extends PreflightCheck
88
{
99
protected array $requiredConfig = [
10-
'database.redis.default',
11-
'database.redis.default.host',
12-
'database.redis.default.password',
13-
'database.redis.default.port',
10+
// @see boot()
1411
];
1512

1613
/**
@@ -21,13 +18,13 @@ class Redis extends PreflightCheck
2118
public function check(Result $result): Result
2219
{
2320
try {
24-
$connection = RedisFacade::connection();
21+
$connection = RedisFacade::connection($this->getConnection());
2522
} catch (\Exception $e) {
2623
return $result->fail($e->getMessage(), $e);
2724
}
2825

2926
if (! $connection->client()->isConnected()) {
30-
return $result->fail('Could not connect to Redis');
27+
return $result->fail('Could not connect to Redis ('.$this->getConnection().')');
3128
}
3229
$info = $connection->client()->info();
3330

@@ -39,4 +36,32 @@ public function check(Result $result): Result
3936
],
4037
]);
4138
}
39+
40+
/**
41+
* Gets the DB connection to check (as specified in database.config).
42+
*/
43+
protected function getConnection(): string
44+
{
45+
return $this->options['connection'] ?? 'default';
46+
}
47+
48+
/**
49+
* Boots the check
50+
*/
51+
protected function boot(): void
52+
{
53+
$connection = $this->getConnection();
54+
$usesAuth = $this->options['auth'] ?? true;
55+
56+
$this->requiredConfig = array_merge(
57+
$this->requiredConfig,
58+
[
59+
"database.redis.${connection}.host",
60+
"database.redis.${connection}.port",
61+
],
62+
$usesAuth ? [
63+
"database.redis.${connection}.password",
64+
] : []
65+
);
66+
}
4267
}

src/Commands/PreflightCheckCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public function handle(Pipeline $pipeline)
9191
*/
9292
protected function bootChecks()
9393
{
94-
$environment = strtolower(App::environment());
94+
$this->environment = $environment = strtolower(App::environment());
9595

9696
try {
9797
if (! config()->has("preflight.checks.{$environment}")) {

tests/Checks/BasePreflightCheckTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ private function assertConfigKeyChecked(PreflightCheck $preflightCheck, string $
5555
config([$configKey => null]);
5656

5757
$configResult = $this->invokeMethod($preflightCheck, 'checkConfig', [new Result('Test\Config')]);
58-
$this->assertTrue(in_array($configKey, $configResult->getRawData()));
58+
$this->assertTrue(in_array($configKey, array_column($configResult->getRawData(), 'key')));
5959

6060
config([$configKey => $originalValue]);
6161
}

tests/Checks/ConfigurationTest.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,33 @@ public function testFailsWhenConfigIsNotSet()
3838
$result = $preflightCheck->check(new Result('Test\Test'));
3939
$this->assertFailed($result);
4040
$data = $result->getRawData();
41-
$this->assertTrue(in_array($config[0], $data));
42-
$this->assertTrue(in_array($config[1], $data));
41+
$foundKeys = array_column($data, 'key');
42+
$this->assertTrue(in_array($config[0], $foundKeys));
43+
$this->assertTrue(in_array($config[1], $foundKeys));
44+
}
45+
46+
/**
47+
* @test
48+
*/
49+
public function testLoadsConfigHints()
50+
{
51+
$config = [
52+
'test1' => uniqid('test1'),
53+
'test2',
54+
];
55+
$preflightCheck = new Configuration($config);
56+
57+
$result = $preflightCheck->check(new Result('Test\Test'));
58+
$this->assertFailed($result);
59+
$data = $result->getRawData();
60+
61+
// first key
62+
$this->assertEquals('test1', $data[0]['key']);
63+
$this->assertEquals($config['test1'], $data[0]['hint']);
64+
65+
// second key
66+
$this->assertEquals('test2', $data[1]['key']);
67+
// No hint set, so this is filtered out
68+
$this->assertArrayNotHasKey('hint', $data[1]);
4369
}
4470
}

tests/Checks/PreflightCheckTest.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,28 @@ public function check(Result $result): Result
157157
$this->assertTrue($report[1]->required());
158158
$this->assertFalse($report[1]->skipped());
159159
}
160+
161+
/**
162+
* @test
163+
*/
164+
public function testLoadsConfigHintsFromOptions()
165+
{
166+
$hints = ['banana' => 'a fruit', 'cucumber' => 'a vegetable'];
167+
$check = new class(['config_hints' => $hints]) extends PreflightCheck {
168+
protected array $requiredConfig = [];
169+
170+
public function check(Result $result): Result
171+
{
172+
return $result->pass();
173+
}
174+
175+
// For testing
176+
public function getConfigHints()
177+
{
178+
return $this->configHints;
179+
}
180+
};
181+
182+
$this->assertEquals($hints, $check->getConfigHints());
183+
}
160184
}

tests/Checks/RedisTest.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ class RedisTest extends BasePreflightCheckTest
1414

1515
/**
1616
* @test
17+
* @dataProvider providesConnectionScenarios
1718
*/
18-
public function testChecksRedisIsAccessible()
19+
public function testChecksRedisIsAccessible($options, $expectedConnection)
1920
{
2021
$mockConnection = Mockery::mock(Connection::class);
2122
RedisFacade::shouldReceive('connection')
2223
->once()
24+
->with($expectedConnection)
2325
->andReturn($mockConnection);
2426

2527
$connectionName = 'TestRedis';
@@ -31,7 +33,7 @@ public function testChecksRedisIsAccessible()
3133
$mockConnection->shouldReceive('getName')->once()->andReturn($connectionName);
3234
$mockConnection->shouldReceive('client->info')->once()->andReturn($connectionInfo);
3335

34-
$preflightCheck = new $this->preflightCheckClass();
36+
$preflightCheck = new $this->preflightCheckClass($options);
3537
$result = $preflightCheck->check(new Result('Test\Test'));
3638

3739
$this->assertPassed($result);
@@ -41,6 +43,27 @@ public function testChecksRedisIsAccessible()
4143
$this->assertEquals($connectionInfo['os'], $resultData['info']['os']);
4244
}
4345

46+
public function providesConnectionScenarios()
47+
{
48+
return [
49+
'No options is default' => [
50+
[], 'default',
51+
],
52+
'Default is default' => [
53+
[
54+
'connection' => 'default',
55+
],
56+
'default',
57+
],
58+
'Banana is banana' => [
59+
[
60+
'connection' => 'banana',
61+
],
62+
'banana',
63+
],
64+
];
65+
}
66+
4467
/**
4568
* @test
4669
*/
@@ -85,4 +108,17 @@ public function testChecksConfigValues()
85108
{
86109
$this->checkConfigValues(new $this->preflightCheckClass());
87110
}
111+
112+
/**
113+
* @test
114+
*/
115+
public function testDoesNotCheckPasswordForNoAuthConfig()
116+
{
117+
$check = new $this->preflightCheckClass(['auth' => false]);
118+
119+
$this->assertArrayNotHasKey(
120+
'database.redis.default.password',
121+
$this->getProtectedProperty($check, 'requiredConfig')
122+
);
123+
}
88124
}

0 commit comments

Comments
 (0)