Implement command to manage redirect rules for a short URL

This commit is contained in:
Alejandro Celaya
2024-03-02 22:44:22 +01:00
parent c36e43e249
commit d8ede3263f
15 changed files with 365 additions and 28 deletions

View File

@@ -13,6 +13,7 @@ use function Shlinkio\Shlink\Core\acceptLanguageToLocales;
use function Shlinkio\Shlink\Core\ArrayUtils\some;
use function Shlinkio\Shlink\Core\normalizeLocale;
use function Shlinkio\Shlink\Core\splitLocale;
use function sprintf;
use function strtolower;
use function trim;
@@ -107,4 +108,17 @@ class RedirectCondition extends AbstractEntity implements JsonSerializable
'matchValue' => $this->matchValue,
];
}
public function toHumanFriendly(): string
{
return match ($this->type) {
RedirectConditionType::DEVICE => sprintf('device is %s', $this->matchValue),
RedirectConditionType::LANGUAGE => sprintf('%s language is accepted', $this->matchValue),
RedirectConditionType::QUERY_PARAM => sprintf(
'query string contains %s=%s',
$this->matchKey,
$this->matchValue,
),
};
}
}

View File

@@ -15,7 +15,7 @@ use function Shlinkio\Shlink\Core\ArrayUtils\every;
class ShortUrlRedirectRule extends AbstractEntity implements JsonSerializable
{
/**
* @param Collection<RedirectCondition> $conditions
* @param Collection<int, RedirectCondition> $conditions
*/
public function __construct(
private readonly ShortUrl $shortUrl, // No need to read this field. It's used by doctrine
@@ -41,6 +41,16 @@ class ShortUrlRedirectRule extends AbstractEntity implements JsonSerializable
$this->conditions->clear();
}
/**
* @template R
* @param callable(RedirectCondition $condition): R $callback
* @return R[]
*/
public function mapConditions(callable $callback): array
{
return $this->conditions->map($callback(...))->toArray();
}
public function jsonSerialize(): array
{
return [

View File

@@ -6,5 +6,5 @@ enum RedirectConditionType: string
{
case DEVICE = 'device';
case LANGUAGE = 'language';
case QUERY_PARAM = 'query';
case QUERY_PARAM = 'query-param';
}

View File

@@ -34,23 +34,6 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
*/
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array
{
return $this->em->wrapInTransaction(fn () => $this->doSetRulesForShortUrl($shortUrl, $data));
}
/**
* @return ShortUrlRedirectRule[]
*/
private function doSetRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array
{
// First, delete existing rules for the short URL
$oldRules = $this->rulesForShortUrl($shortUrl);
foreach ($oldRules as $oldRule) {
$oldRule->clearConditions(); // This will trigger the orphan removal of old conditions
$this->em->remove($oldRule);
}
$this->em->flush();
// Then insert new rules
$rules = [];
foreach ($data->rules as $index => $rule) {
$rule = new ShortUrlRedirectRule(
@@ -64,9 +47,30 @@ readonly class ShortUrlRedirectRuleService implements ShortUrlRedirectRuleServic
);
$rules[] = $rule;
$this->em->persist($rule);
}
$this->saveRulesForShortUrl($shortUrl, $rules);
return $rules;
}
/**
* @param ShortUrlRedirectRule[] $rules
*/
public function saveRulesForShortUrl(ShortUrl $shortUrl, array $rules): void
{
$this->em->wrapInTransaction(function () use ($shortUrl, $rules): void {
// First, delete existing rules for the short URL
$oldRules = $this->rulesForShortUrl($shortUrl);
foreach ($oldRules as $oldRule) {
$oldRule->clearConditions(); // This will trigger the orphan removal of old conditions
$this->em->remove($oldRule);
}
$this->em->flush();
// Then insert new rules
foreach ($rules as $rule) {
$this->em->persist($rule);
}
});
}
}

View File

@@ -17,4 +17,9 @@ interface ShortUrlRedirectRuleServiceInterface
* @return ShortUrlRedirectRule[]
*/
public function setRulesForShortUrl(ShortUrl $shortUrl, RedirectRulesData $data): array;
/**
* @param ShortUrlRedirectRule[] $rules
*/
public function saveRulesForShortUrl(ShortUrl $shortUrl, array $rules): void;
}

View File

@@ -124,6 +124,9 @@ class ShortUrlInputFilter extends InputFilter
$this->add($apiKeyInput);
}
/**
* @todo Extract to its own validator class
*/
public static function longUrlValidators(bool $allowNull = false): Validator\ValidatorChain
{
$emptyModifiers = [