Skip to content

Commit 4fbdfb3

Browse files
authored
Merge pull request #78 from MacPaw/develop
release
2 parents f1a0883 + 0c5ed05 commit 4fbdfb3

11 files changed

+489
-2
lines changed

README.md

+10
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ symfony_health_check:
4545
ping_checks:
4646
- id: symfony_health_check.status_up_check
4747
```
48+
To perform redis check you need use provide its dsn in the config:
49+
```yaml
50+
symfony_health_check:
51+
health_checks:
52+
...
53+
- id: symfony_health_check.redis_check
54+
55+
redis_dsn: 'redis://localhost:6379'
56+
```
57+
4858
Change response code:
4959
- default response code is 200.
5060
- determine your custom response code in case of some check fails (Response code must be a valid HTTP status code)

composer.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"symfony/phpunit-bridge": "^3.4 || ^4.1.12 || ^5.0 || ^6.0 || ^7.0",
4040
"phpunit/phpunit": "^8.5 || ^9.0",
4141
"symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
42-
"symfony/browser-kit": "^3.4 || ^4.4 || ^5.0 || ^6.0 || ^7.0"
42+
"symfony/browser-kit": "^3.4 || ^4.4 || ^5.0 || ^6.0 || ^7.0",
43+
"symfony/cache": "^3.4 || ^4.4 || ^5.0 || ^6.0 || ^7.0",
44+
"predis/predis": "^2.3"
4345
},
4446
"autoload": {
4547
"psr-4": {

src/Adapter/RedisAdapterWrapper.php

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SymfonyHealthCheckBundle\Adapter;
6+
7+
use Symfony\Component\Cache\Adapter\RedisAdapter;
8+
9+
/**
10+
* @codeCoverageIgnore - simple wrapper of static methods for adapter class
11+
*/
12+
class RedisAdapterWrapper
13+
{
14+
/**
15+
* @param string $dsn
16+
* @param array $options
17+
*
18+
* @return \Predis\ClientInterface|\Redis|\RedisArray|\RedisCluster|\Relay\Relay
19+
*/
20+
public function createConnection(string $dsn, array $options = []): object
21+
{
22+
return RedisAdapter::createConnection($dsn, $options);
23+
}
24+
}

src/Check/RedisCheck.php

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SymfonyHealthCheckBundle\Check;
6+
7+
use SymfonyHealthCheckBundle\Dto\Response;
8+
use SymfonyHealthCheckBundle\Adapter\RedisAdapterWrapper;
9+
10+
class RedisCheck implements CheckInterface
11+
{
12+
private const CHECK_RESULT_NAME = 'redis_check';
13+
14+
private RedisAdapterWrapper $redisAdapter;
15+
private string $redisDsn;
16+
17+
public function __construct(RedisAdapterWrapper $redisAdapter, string $redisDsn)
18+
{
19+
$this->redisAdapter = $redisAdapter;
20+
$this->redisDsn = $redisDsn;
21+
}
22+
23+
public function check(): Response
24+
{
25+
try {
26+
$redisConnection = $this->redisAdapter->createConnection($this->redisDsn);
27+
28+
switch (true) {
29+
case $redisConnection instanceof \Redis:
30+
$result = $this->checkForDefaultRedisClient($redisConnection);
31+
32+
break;
33+
case $redisConnection instanceof \Predis\ClientInterface:
34+
$result = $this->checkForPredisClient($redisConnection);
35+
36+
break;
37+
case $redisConnection instanceof \RedisArray:
38+
$result = $this->checkForRedisArrayClient($redisConnection);
39+
40+
break;
41+
default:
42+
throw new \RuntimeException(sprintf(
43+
'Unsupported Redis client type: %s',
44+
get_class($redisConnection),
45+
));
46+
}
47+
48+
if (!$result) {
49+
return new Response(self::CHECK_RESULT_NAME, false, 'Redis ping failed.');
50+
}
51+
52+
return new Response(self::CHECK_RESULT_NAME, true, 'ok');
53+
} catch (\Throwable $e) {
54+
return new Response(self::CHECK_RESULT_NAME, false, $e->getMessage());
55+
}
56+
}
57+
58+
private function checkForDefaultRedisClient(\Redis $client): bool
59+
{
60+
$response = $client->ping();
61+
62+
if (is_bool($response)) {
63+
return $response;
64+
}
65+
66+
return $this->isValidPingResponse($response);
67+
}
68+
69+
private function checkForPredisClient(\Predis\ClientInterface $client): bool
70+
{
71+
$response = $client->ping();
72+
73+
if (is_bool($response)) {
74+
return $response;
75+
}
76+
77+
return $this->isValidPingResponse($response);
78+
}
79+
80+
private function checkForRedisArrayClient(\RedisArray $client): bool
81+
{
82+
$response = $client->ping();
83+
84+
if (is_bool($response)) {
85+
return $response;
86+
}
87+
88+
// invalid configuration, RedisClient have different response, than one, provided by RedisArray in fact.
89+
// @phpstan-ignore-next-line
90+
foreach ($response as $pingResult) {
91+
if (is_bool($pingResult)) {
92+
continue;
93+
}
94+
95+
if (!$this->isValidPingResponse($pingResult)) {
96+
return false;
97+
}
98+
}
99+
100+
return true;
101+
}
102+
103+
private function isValidPingResponse(string $response): bool
104+
{
105+
return in_array(strtolower($response), ['pong', '+pong'], true);
106+
}
107+
}

src/DependencyInjection/Configuration.php

+9
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,15 @@ public function getConfigTreeBuilder(): TreeBuilder
4040
->thenInvalid('The health_error_response_code must be valid HTTP status code or null.')
4141
->end()
4242
->end()
43+
->variableNode('redis_dsn')
44+
->defaultValue(null)
45+
->validate()
46+
->ifTrue(function ($value) {
47+
return $value !== null && !is_string($value);
48+
})
49+
->thenInvalid('The redis_dsn must be a string or null.')
50+
->end()
51+
->end()
4352
->arrayNode('health_checks')
4453
->prototype('array')
4554
->children()

src/DependencyInjection/SymfonyHealthCheckExtension.php

+15
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace SymfonyHealthCheckBundle\DependencyInjection;
66

7+
use Composer\InstalledVersions;
78
use Symfony\Component\Config\FileLocator;
89
use Symfony\Component\DependencyInjection\ContainerBuilder;
910
use Symfony\Component\DependencyInjection\Reference;
@@ -23,6 +24,9 @@ public function load(array $configs, ContainerBuilder $container): void
2324
$loader->load('controller.xml');
2425

2526
$this->loadHealthChecks($config, $loader, $container);
27+
28+
$container->getDefinition('symfony_health_check.redis_check')
29+
->setArgument(1, $config['redis_dsn']);
2630
}
2731

2832
private function loadHealthChecks(
@@ -34,6 +38,17 @@ private function loadHealthChecks(
3438

3539
$healthCheckCollection = $container->findDefinition(HealthController::class);
3640

41+
$usedChecks = array_column(array_merge($config['health_checks'], $config['ping_checks']), 'id');
42+
if (in_array('symfony_health_check.redis_check', $usedChecks)) {
43+
if (!InstalledVersions::isInstalled('symfony/cache')) {
44+
throw new \RuntimeException('To use RedisCheck you need to install symfony/cache package.');
45+
}
46+
47+
if (empty($config['redis_dsn'])) {
48+
throw new \RuntimeException('To use RedisCheck you need to configure redis_dsn parameter.');
49+
}
50+
}
51+
3752
foreach ($config['health_checks'] as $healthCheckConfig) {
3853
$healthCheckDefinition = new Reference($healthCheckConfig['id']);
3954
$healthCheckCollection->addMethodCall('addHealthCheck', [$healthCheckDefinition]);

src/Resources/config/health_checks.xml

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
http://symfony.com/schema/dic/services/services-1.0.xsd">
66
<services>
77
<defaults autowire="true" autoconfigure="true" public="true" />
8+
<service id="symfony_health_check.redis_adapter_wrapper" class="SymfonyHealthCheckBundle\Adapter\RedisAdapterWrapper" public="false"/>
9+
810
<service id="symfony_health_check.doctrine_check" class="SymfonyHealthCheckBundle\Check\DoctrineORMCheck">
911
<argument type="service" id="service_container"/>
1012
<deprecated package="macpaw/symfony-health-check-bundle" version="1.4.2">The "%service_id%" service alias is deprecated, use symfony_health_check.doctrine_orm_check instead</deprecated>
@@ -15,6 +17,9 @@
1517
<service id="symfony_health_check.doctrine_odm_check" class="SymfonyHealthCheckBundle\Check\DoctrineODMCheck">
1618
<argument type="service" id="service_container"/>
1719
</service>
20+
<service id="symfony_health_check.redis_check" class="SymfonyHealthCheckBundle\Check\RedisCheck">
21+
<argument type="service" id="symfony_health_check.redis_adapter_wrapper"/>
22+
</service>
1823
<service id="symfony_health_check.environment_check" class="SymfonyHealthCheckBundle\Check\EnvironmentCheck">
1924
<argument type="service" id="service_container"/>
2025
</service>

tests/Integration/DependencyInjection/ConfigurationTest.php

+32
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public function testProcessConfigurationWithDefaultConfiguration(): void
1515
$expectedBundleDefaultConfig = [
1616
'ping_error_response_code' => null,
1717
'health_error_response_code' => null,
18+
'redis_dsn' => null,
1819
'health_checks' => [],
1920
'ping_checks' => [],
2021
];
@@ -40,6 +41,7 @@ public function testProcessConfigurationHealthChecks(): void
4041
'ping_checks' => [],
4142
'ping_error_response_code' => null,
4243
'health_error_response_code' => null,
44+
'redis_dsn' => null,
4345
];
4446
$new = ['health_checks' => [
4547
['id' => 'symfony_health_check.doctrine_check']
@@ -60,6 +62,7 @@ public function testProcessConfigurationPing(): void
6062
],
6163
'ping_error_response_code' => null,
6264
'health_error_response_code' => null,
65+
'redis_dsn' => null,
6366
];
6467
$new = ['health_checks' => [], 'ping_checks' => [
6568
['id' => 'symfony_health_check.doctrine_check']
@@ -82,6 +85,7 @@ public function testProcessConfigurationPingAndHealthChecks(): void
8285
],
8386
'ping_error_response_code' => null,
8487
'health_error_response_code' => null,
88+
'redis_dsn' => null,
8589
];
8690
$new = [
8791
'health_checks' => [['id' => 'symfony_health_check.doctrine_check']],
@@ -105,6 +109,7 @@ public function testProcessConfigurationCustomErrorCode(): void
105109
],
106110
'ping_error_response_code' => 404,
107111
'health_error_response_code' => 500,
112+
'redis_dsn' => null,
108113
];
109114
$new = [
110115
'health_checks' => [['id' => 'symfony_health_check.doctrine_check']],
@@ -119,6 +124,33 @@ public function testProcessConfigurationCustomErrorCode(): void
119124
);
120125
}
121126

127+
public function testItProcessConfigurationWithRedisDsn(): void
128+
{
129+
$expectedConfig = [
130+
'health_checks' => [
131+
['id' => 'symfony_health_check.doctrine_check']
132+
],
133+
'ping_checks' => [
134+
['id' => 'symfony_health_check.doctrine_check']
135+
],
136+
'ping_error_response_code' => 404,
137+
'health_error_response_code' => 500,
138+
'redis_dsn' => 'redis://redis',
139+
];
140+
$new = [
141+
'health_checks' => [['id' => 'symfony_health_check.doctrine_check']],
142+
'ping_checks' => [['id' => 'symfony_health_check.doctrine_check']],
143+
'ping_error_response_code' => 404,
144+
'health_error_response_code' => 500,
145+
'redis_dsn' => 'redis://redis',
146+
];
147+
148+
self::assertSame(
149+
$expectedConfig,
150+
$this->processConfiguration($new)
151+
);
152+
}
153+
122154
private function processConfiguration(array $values): array
123155
{
124156
$processor = new Processor();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
symfony_health_check:
2+
health_checks:
3+
- id: symfony_health_check.redis_check
4+
ping_checks:
5+
- id: symfony_health_check.doctrine_check

tests/Integration/DependencyInjection/SymfonyHealthCheckExtensionTest.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,28 @@ public function testWithEmptyConfigPing(): void
4646
}
4747
}
4848

49+
public function testWithEmptyRedisDsnConfig(): void
50+
{
51+
$this->expectException(\RuntimeException::class);
52+
$this->expectExceptionMessage('To use RedisCheck you need to configure redis_dsn parameter.');
53+
54+
$this->createContainerFromFixture('error_redis_check_bundle_config');
55+
}
56+
4957
public function testWithFullConfig(): void
5058
{
5159
$container = $this->createContainerFromFixture('filled_bundle_config');
5260

53-
self::assertCount(8, $container->getDefinitions());
61+
self::assertCount(10, $container->getDefinitions());
5462
self::assertArrayHasKey(HealthController::class, $container->getDefinitions());
5563
self::assertArrayHasKey(PingController::class, $container->getDefinitions());
5664
self::assertArrayHasKey('symfony_health_check.doctrine_check', $container->getDefinitions()); #deprecated
5765
self::assertArrayHasKey('symfony_health_check.doctrine_orm_check', $container->getDefinitions());
5866
self::assertArrayHasKey('symfony_health_check.doctrine_odm_check', $container->getDefinitions());
5967
self::assertArrayHasKey('symfony_health_check.environment_check', $container->getDefinitions());
6068
self::assertArrayHasKey('symfony_health_check.status_up_check', $container->getDefinitions());
69+
self::assertArrayHasKey('symfony_health_check.redis_check', $container->getDefinitions());
70+
self::assertArrayHasKey('symfony_health_check.redis_adapter_wrapper', $container->getDefinitions());
6171
}
6272

6373
private function createContainerFromFixture(string $fixtureFile): ContainerBuilder

0 commit comments

Comments
 (0)