Skip to content

Commit c61a11c

Browse files
authored
Merge pull request #251 from shlinkio/develop
Release 9.7.0
2 parents 3980a74 + 49abf71 commit c61a11c

File tree

10 files changed

+232
-13
lines changed

10 files changed

+232
-13
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org).
66

7+
## [9.7.0] - 2025-11-01
8+
### Added
9+
* Add `REDIS_SERVERS_USER` and `REDIS_SERVERS_PASSWORD` config options.
10+
11+
### Changed
12+
* *Nothing*
13+
14+
### Deprecated
15+
* *Nothing*
16+
17+
### Removed
18+
* *Nothing*
19+
20+
### Fixed
21+
* *Nothing*
22+
23+
724
## [9.6.0] - 2025-07-24
825
### Added
926
* Add `REAL_TIME_UPDATES_TOPICS` config option.

composer.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,23 @@
1313
],
1414
"require": {
1515
"php": "^8.3",
16-
"laminas/laminas-config-aggregator": "^1.17",
17-
"laminas/laminas-servicemanager": "^4.3 || ^3.23",
16+
"laminas/laminas-config-aggregator": "^1.18",
17+
"laminas/laminas-servicemanager": "^4.4 || ^3.23",
1818
"laminas/laminas-stdlib": "^3.20",
1919
"shlinkio/shlink-config": "^4.0",
20-
"symfony/console": "^7.2",
21-
"symfony/filesystem": "^7.2",
22-
"symfony/process": "^7.2",
20+
"symfony/console": "^7.3",
21+
"symfony/filesystem": "^7.3",
22+
"symfony/process": "^7.3",
2323
"webimpress/safe-writer": "^2.2"
2424
},
2525
"require-dev": {
2626
"devster/ubench": "^2.1",
2727
"phpstan/phpstan": "^2.1",
2828
"phpstan/phpstan-phpunit": "^2.0",
29-
"phpunit/phpunit": "^12.0",
29+
"phpunit/phpunit": "^12.4",
3030
"roave/security-advisories": "dev-master",
31-
"shlinkio/php-coding-standard": "~2.4.0",
32-
"symfony/var-dumper": "^7.2"
31+
"shlinkio/php-coding-standard": "~2.5.0",
32+
"symfony/var-dumper": "^7.3"
3333
},
3434
"autoload": {
3535
"psr-4": {

config/config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@
121121
'INTEGRATIONS' => [
122122
'Redis > servers' => Config\Option\Redis\RedisServersConfigOption::class,
123123
'Redis > sentinels service' => Config\Option\Redis\RedisSentinelServiceConfigOption::class,
124+
'Redis > servers user' => Config\Option\Redis\RedisServersUserConfigOption::class,
125+
'Redis > servers password' => Config\Option\Redis\RedisServersPasswordConfigOption::class,
124126
'Redis > Pub/sub enabled' => Config\Option\Redis\RedisPubSubConfigOption::class,
125127
Config\Option\Mercure\EnableMercureConfigOption::class,
126128
'Mercure > Public URL' => Config\Option\Mercure\MercurePublicUrlConfigOption::class,
@@ -171,6 +173,8 @@
171173
Config\Option\Robots\RobotsAllowAllShortUrlsConfigOption::class => InvokableFactory::class,
172174
Config\Option\Redis\RedisServersConfigOption::class => InvokableFactory::class,
173175
Config\Option\Redis\RedisSentinelServiceConfigOption::class => InvokableFactory::class,
176+
Config\Option\Redis\RedisServersUserConfigOption::class => InvokableFactory::class,
177+
Config\Option\Redis\RedisServersPasswordConfigOption::class => InvokableFactory::class,
174178
Config\Option\Redis\RedisPubSubConfigOption::class => InvokableFactory::class,
175179
Config\Option\UrlShortener\ShortCodeLengthOption::class => InvokableFactory::class,
176180
Config\Option\Mercure\EnableMercureConfigOption::class => InvokableFactory::class,

src/Config/Option/Redis/RedisSentinelServiceConfigOption.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
class RedisSentinelServiceConfigOption extends BaseConfigOption implements DependentConfigOptionInterface
1212
{
13+
public const string ENV_VAR = 'REDIS_SENTINEL_SERVICE';
14+
1315
public function getEnvVar(): string
1416
{
15-
return 'REDIS_SENTINEL_SERVICE';
17+
return self::ENV_VAR;
1618
}
1719

1820
public function shouldBeAsked(array $currentOptions): bool
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shlinkio\Shlink\Installer\Config\Option\Redis;
6+
7+
use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption;
8+
use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface;
9+
use Symfony\Component\Console\Style\StyleInterface;
10+
11+
class RedisServersPasswordConfigOption extends BaseConfigOption implements DependentConfigOptionInterface
12+
{
13+
public function getEnvVar(): string
14+
{
15+
return 'REDIS_SERVERS_PASSWORD';
16+
}
17+
18+
public function shouldBeAsked(array $currentOptions): bool
19+
{
20+
$isSentinelEnabled = $currentOptions[RedisSentinelServiceConfigOption::ENV_VAR] ?? null;
21+
return $isSentinelEnabled !== null && parent::shouldBeAsked($currentOptions);
22+
}
23+
24+
public function ask(StyleInterface $io, array $currentOptions): string|null
25+
{
26+
return $io->ask('Provide a password for your redis connection (leave empty if ACL is not required)');
27+
}
28+
29+
public function getDependentOption(): string
30+
{
31+
return RedisSentinelServiceConfigOption::class;
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Shlinkio\Shlink\Installer\Config\Option\Redis;
6+
7+
use Shlinkio\Shlink\Installer\Config\Option\BaseConfigOption;
8+
use Shlinkio\Shlink\Installer\Config\Option\DependentConfigOptionInterface;
9+
use Symfony\Component\Console\Style\StyleInterface;
10+
11+
class RedisServersUserConfigOption extends BaseConfigOption implements DependentConfigOptionInterface
12+
{
13+
public function getEnvVar(): string
14+
{
15+
return 'REDIS_SERVERS_USER';
16+
}
17+
18+
public function shouldBeAsked(array $currentOptions): bool
19+
{
20+
$isSentinelEnabled = $currentOptions[RedisSentinelServiceConfigOption::ENV_VAR] ?? null;
21+
return $isSentinelEnabled !== null && parent::shouldBeAsked($currentOptions);
22+
}
23+
24+
public function ask(StyleInterface $io, array $currentOptions): string|null
25+
{
26+
return $io->ask('Provide a username for your redis connection (leave empty if ACL is not required)');
27+
}
28+
29+
public function getDependentOption(): string
30+
{
31+
return RedisSentinelServiceConfigOption::class;
32+
}
33+
}

src/Config/Option/UrlShortener/AppendExtraPathConfigOption.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ public function getEnvVar(): string
1818
public function ask(StyleInterface $io, array $currentOptions): bool
1919
{
2020
return $io->confirm(
21-
//@codingStandardsIgnoreStart
21+
// phpcs:disable
2222
<<<FOO
2323
Do you want Shlink to redirect short URLs as soon as the first segment of the path matches a short code, appending the rest to the long URL?
2424
* {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}/[...extraPath]
2525
* https://example.com/abc123 -> https://www.twitter.com
2626
* https://example.com/abc123/shlinkio -> https://www.twitter.com/shlinkio
2727
2828
FOO,
29-
//@codingStandardsIgnoreEnd
29+
// phpcs:disable
3030
false,
3131
);
3232
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ShlinkioTest\Shlink\Installer\Config\Option\Redis;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use PHPUnit\Framework\TestCase;
10+
use Shlinkio\Shlink\Installer\Config\Option\Redis\RedisSentinelServiceConfigOption;
11+
use Shlinkio\Shlink\Installer\Config\Option\Redis\RedisServersPasswordConfigOption;
12+
use Symfony\Component\Console\Style\StyleInterface;
13+
14+
class RedisServersPasswordConfigOptionTest extends TestCase
15+
{
16+
private RedisServersPasswordConfigOption $configOption;
17+
18+
public function setUp(): void
19+
{
20+
$this->configOption = new RedisServersPasswordConfigOption();
21+
}
22+
23+
#[Test]
24+
public function returnsExpectedEnvVar(): void
25+
{
26+
self::assertEquals('REDIS_SERVERS_PASSWORD', $this->configOption->getEnvVar());
27+
}
28+
29+
#[Test, DataProvider('provideValidAnswers')]
30+
public function expectedQuestionIsAsked(string|null $answer): void
31+
{
32+
$io = $this->createMock(StyleInterface::class);
33+
$io->expects($this->once())->method('ask')->with(
34+
'Provide a password for your redis connection (leave empty if ACL is not required)',
35+
)->willReturn($answer);
36+
37+
$results = $this->configOption->ask($io, []);
38+
39+
self::assertEquals($answer, $results);
40+
}
41+
42+
public static function provideValidAnswers(): iterable
43+
{
44+
yield ['the_answer'];
45+
yield [null];
46+
}
47+
48+
#[Test, DataProvider('provideCurrentOptions')]
49+
public function shouldBeCalledOnlyIfItDoesNotYetExist(array $currentOptions, bool $expected): void
50+
{
51+
self::assertEquals($expected, $this->configOption->shouldBeAsked($currentOptions));
52+
}
53+
54+
public static function provideCurrentOptions(): iterable
55+
{
56+
yield 'not exists in config' => [[], false];
57+
yield 'sentinel enabled in config' => [[RedisSentinelServiceConfigOption::ENV_VAR => 'bar'], true];
58+
}
59+
60+
#[Test]
61+
public function dependsOnRedisServer(): void
62+
{
63+
self::assertEquals(RedisSentinelServiceConfigOption::class, $this->configOption->getDependentOption());
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ShlinkioTest\Shlink\Installer\Config\Option\Redis;
6+
7+
use PHPUnit\Framework\Attributes\DataProvider;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use PHPUnit\Framework\TestCase;
10+
use Shlinkio\Shlink\Installer\Config\Option\Redis\RedisSentinelServiceConfigOption;
11+
use Shlinkio\Shlink\Installer\Config\Option\Redis\RedisServersUserConfigOption;
12+
use Symfony\Component\Console\Style\StyleInterface;
13+
14+
class RedisServersUserConfigOptionTest extends TestCase
15+
{
16+
private RedisServersUserConfigOption $configOption;
17+
18+
public function setUp(): void
19+
{
20+
$this->configOption = new RedisServersUserConfigOption();
21+
}
22+
23+
#[Test]
24+
public function returnsExpectedEnvVar(): void
25+
{
26+
self::assertEquals('REDIS_SERVERS_USER', $this->configOption->getEnvVar());
27+
}
28+
29+
#[Test, DataProvider('provideValidAnswers')]
30+
public function expectedQuestionIsAsked(string|null $answer): void
31+
{
32+
$io = $this->createMock(StyleInterface::class);
33+
$io->expects($this->once())->method('ask')->with(
34+
'Provide a username for your redis connection (leave empty if ACL is not required)',
35+
)->willReturn($answer);
36+
37+
$results = $this->configOption->ask($io, []);
38+
39+
self::assertEquals($answer, $results);
40+
}
41+
42+
public static function provideValidAnswers(): iterable
43+
{
44+
yield ['the_answer'];
45+
yield [null];
46+
}
47+
48+
#[Test, DataProvider('provideCurrentOptions')]
49+
public function shouldBeCalledOnlyIfItDoesNotYetExist(array $currentOptions, bool $expected): void
50+
{
51+
self::assertEquals($expected, $this->configOption->shouldBeAsked($currentOptions));
52+
}
53+
54+
public static function provideCurrentOptions(): iterable
55+
{
56+
yield 'not exists in config' => [[], false];
57+
yield 'sentinel enabled in config' => [[RedisSentinelServiceConfigOption::ENV_VAR => 'bar'], true];
58+
}
59+
60+
#[Test]
61+
public function dependsOnRedisServer(): void
62+
{
63+
self::assertEquals(RedisSentinelServiceConfigOption::class, $this->configOption->getDependentOption());
64+
}
65+
}

test/Config/Option/UrlShortener/AppendExtraPathConfigOptionTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ public function expectedQuestionIsAsked(): void
2929
{
3030
$io = $this->createMock(StyleInterface::class);
3131
$io->expects($this->once())->method('confirm')->with(
32-
//@codingStandardsIgnoreStart
32+
// phpcs:disable
3333
<<<FOO
3434
Do you want Shlink to redirect short URLs as soon as the first segment of the path matches a short code, appending the rest to the long URL?
3535
* {shortDomain}/{shortCode}/[...extraPath] -> {longUrl}/[...extraPath]
3636
* https://example.com/abc123 -> https://www.twitter.com
3737
* https://example.com/abc123/shlinkio -> https://www.twitter.com/shlinkio
3838
3939
FOO,
40-
//@codingStandardsIgnoreEnd
40+
// phpcs:disable
4141
false,
4242
)->willReturn(true);
4343

0 commit comments

Comments
 (0)