mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-06 23:33:13 +08:00
Convert EditShortUrlCommand into invokable command
This commit is contained in:
@@ -4,55 +4,49 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
||||||
|
|
||||||
use Shlinkio\Shlink\CLI\Input\ShortUrlDataInput;
|
use Shlinkio\Shlink\CLI\Command\ShortUrl\Input\ShortUrlDataInput;
|
||||||
use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput;
|
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition;
|
||||||
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\Argument;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Attribute\MapInput;
|
||||||
|
use Symfony\Component\Console\Attribute\Option;
|
||||||
use Symfony\Component\Console\Command\Command;
|
use Symfony\Component\Console\Command\Command;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: EditShortUrlCommand::NAME,
|
||||||
|
description: 'Edit an existing short URL',
|
||||||
|
)]
|
||||||
class EditShortUrlCommand extends Command
|
class EditShortUrlCommand extends Command
|
||||||
{
|
{
|
||||||
public const string NAME = 'short-url:edit';
|
public const string NAME = 'short-url:edit';
|
||||||
|
|
||||||
private readonly ShortUrlDataInput $shortUrlDataInput;
|
|
||||||
private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ShortUrlServiceInterface $shortUrlService,
|
private readonly ShortUrlServiceInterface $shortUrlService,
|
||||||
private readonly ShortUrlStringifierInterface $stringifier,
|
private readonly ShortUrlStringifierInterface $stringifier,
|
||||||
) {
|
) {
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
|
|
||||||
$this->shortUrlDataInput = new ShortUrlDataInput($this, longUrlAsOption: true);
|
|
||||||
$this->shortUrlIdentifierInput = new ShortUrlIdentifierInput(
|
|
||||||
$this,
|
|
||||||
shortCodeDesc: 'The short code to edit',
|
|
||||||
domainDesc: 'The domain to which the short URL is attached.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function configure(): void
|
public function __invoke(
|
||||||
{
|
SymfonyStyle $io,
|
||||||
$this
|
#[Argument('The short code to edit')] string $shortCode,
|
||||||
->setName(self::NAME)
|
#[MapInput] ShortUrlDataInput $data,
|
||||||
->setDescription('Edit an existing short URL');
|
#[Option('The domain to which the short URL is attached', shortcut: 'd')] string|null $domain = null,
|
||||||
}
|
#[Option('The long URL to set', shortcut: 'l')] string|null $longUrl = null,
|
||||||
|
): int {
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain);
|
||||||
{
|
|
||||||
$io = new SymfonyStyle($input, $output);
|
|
||||||
$identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$shortUrl = $this->shortUrlService->updateShortUrl(
|
$shortUrl = $this->shortUrlService->updateShortUrl(
|
||||||
$identifier,
|
$identifier,
|
||||||
$this->shortUrlDataInput->toShortUrlEdition($input),
|
ShortUrlEdition::fromRawData($data->toArray()),
|
||||||
);
|
);
|
||||||
|
|
||||||
$io->success(sprintf('Short URL "%s" properly edited', $this->stringifier->stringify($shortUrl)));
|
$io->success(sprintf('Short URL "%s" properly edited', $this->stringifier->stringify($shortUrl)));
|
||||||
|
|||||||
@@ -1,128 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Input;
|
|
||||||
|
|
||||||
use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions;
|
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
|
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition;
|
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
|
|
||||||
final readonly class ShortUrlDataInput
|
|
||||||
{
|
|
||||||
private TagsOption $tagsOption;
|
|
||||||
|
|
||||||
public function __construct(Command $command, private bool $longUrlAsOption = false)
|
|
||||||
{
|
|
||||||
if ($longUrlAsOption) {
|
|
||||||
$command->addOption('long-url', 'l', InputOption::VALUE_REQUIRED, 'The long URL to set');
|
|
||||||
} else {
|
|
||||||
$command->addArgument('longUrl', InputArgument::REQUIRED, 'The long URL to set');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->tagsOption = new TagsOption($command, 'Tags to apply to the short URL');
|
|
||||||
|
|
||||||
$command
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::VALID_SINCE->value,
|
|
||||||
ShortUrlDataOption::VALID_SINCE->shortcut(),
|
|
||||||
InputOption::VALUE_REQUIRED,
|
|
||||||
'The date from which this short URL will be valid. '
|
|
||||||
. 'If someone tries to access it before this date, it will not be found.',
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::VALID_UNTIL->value,
|
|
||||||
ShortUrlDataOption::VALID_UNTIL->shortcut(),
|
|
||||||
InputOption::VALUE_REQUIRED,
|
|
||||||
'The date until which this short URL will be valid. '
|
|
||||||
. 'If someone tries to access it after this date, it will not be found.',
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::MAX_VISITS->value,
|
|
||||||
ShortUrlDataOption::MAX_VISITS->shortcut(),
|
|
||||||
InputOption::VALUE_REQUIRED,
|
|
||||||
'This will limit the number of visits for this short URL.',
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::TITLE->value,
|
|
||||||
ShortUrlDataOption::TITLE->shortcut(),
|
|
||||||
InputOption::VALUE_REQUIRED,
|
|
||||||
'A descriptive title for the short URL.',
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::CRAWLABLE->value,
|
|
||||||
ShortUrlDataOption::CRAWLABLE->shortcut(),
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Tells if this short URL will be included as "Allow" in Shlink\'s robots.txt.',
|
|
||||||
)
|
|
||||||
->addOption(
|
|
||||||
ShortUrlDataOption::NO_FORWARD_QUERY->value,
|
|
||||||
ShortUrlDataOption::NO_FORWARD_QUERY->shortcut(),
|
|
||||||
InputOption::VALUE_NONE,
|
|
||||||
'Disables the forwarding of the query string to the long URL, when the short URL is visited.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toShortUrlEdition(InputInterface $input): ShortUrlEdition
|
|
||||||
{
|
|
||||||
return ShortUrlEdition::fromRawData($this->getCommonData($input));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function toShortUrlCreation(
|
|
||||||
InputInterface $input,
|
|
||||||
UrlShortenerOptions $options,
|
|
||||||
string $customSlugField,
|
|
||||||
string $shortCodeLengthField,
|
|
||||||
string $pathPrefixField,
|
|
||||||
string $findIfExistsField,
|
|
||||||
string $domainField,
|
|
||||||
): ShortUrlCreation {
|
|
||||||
$shortCodeLength = $input->getOption($shortCodeLengthField) ?? $options->defaultShortCodesLength;
|
|
||||||
return ShortUrlCreation::fromRawData([
|
|
||||||
...$this->getCommonData($input),
|
|
||||||
ShortUrlInputFilter::CUSTOM_SLUG => $input->getOption($customSlugField),
|
|
||||||
ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength,
|
|
||||||
ShortUrlInputFilter::PATH_PREFIX => $input->getOption($pathPrefixField),
|
|
||||||
ShortUrlInputFilter::FIND_IF_EXISTS => $input->getOption($findIfExistsField),
|
|
||||||
ShortUrlInputFilter::DOMAIN => $input->getOption($domainField),
|
|
||||||
], $options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getCommonData(InputInterface $input): array
|
|
||||||
{
|
|
||||||
$longUrl = $this->longUrlAsOption ? $input->getOption('long-url') : $input->getArgument('longUrl');
|
|
||||||
$data = [ShortUrlInputFilter::LONG_URL => $longUrl];
|
|
||||||
|
|
||||||
// Avoid setting arguments that were not explicitly provided.
|
|
||||||
// This is important when editing short URLs and should not make a difference when creating.
|
|
||||||
if (ShortUrlDataOption::VALID_SINCE->wasProvided($input)) {
|
|
||||||
$data[ShortUrlInputFilter::VALID_SINCE] = $input->getOption('valid-since');
|
|
||||||
}
|
|
||||||
if (ShortUrlDataOption::VALID_UNTIL->wasProvided($input)) {
|
|
||||||
$data[ShortUrlInputFilter::VALID_UNTIL] = $input->getOption('valid-until');
|
|
||||||
}
|
|
||||||
if (ShortUrlDataOption::MAX_VISITS->wasProvided($input)) {
|
|
||||||
$maxVisits = $input->getOption('max-visits');
|
|
||||||
$data[ShortUrlInputFilter::MAX_VISITS] = $maxVisits !== null ? (int) $maxVisits : null;
|
|
||||||
}
|
|
||||||
if ($this->tagsOption->exists($input)) {
|
|
||||||
$data[ShortUrlInputFilter::TAGS] = $this->tagsOption->get($input);
|
|
||||||
}
|
|
||||||
if (ShortUrlDataOption::TITLE->wasProvided($input)) {
|
|
||||||
$data[ShortUrlInputFilter::TITLE] = $input->getOption('title');
|
|
||||||
}
|
|
||||||
if (ShortUrlDataOption::CRAWLABLE->wasProvided($input)) {
|
|
||||||
$data[ShortUrlInputFilter::CRAWLABLE] = $input->getOption('crawlable');
|
|
||||||
}
|
|
||||||
if (ShortUrlDataOption::NO_FORWARD_QUERY->wasProvided($input)) {
|
|
||||||
$data[ShortUrlInputFilter::FORWARD_QUERY] = !$input->getOption('no-forward-query');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Input;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
|
|
||||||
use function sprintf;
|
|
||||||
|
|
||||||
enum ShortUrlDataOption: string
|
|
||||||
{
|
|
||||||
case VALID_SINCE = 'valid-since';
|
|
||||||
case VALID_UNTIL = 'valid-until';
|
|
||||||
case MAX_VISITS = 'max-visits';
|
|
||||||
case TITLE = 'title';
|
|
||||||
case CRAWLABLE = 'crawlable';
|
|
||||||
case NO_FORWARD_QUERY = 'no-forward-query';
|
|
||||||
|
|
||||||
public function shortcut(): string|null
|
|
||||||
{
|
|
||||||
return match ($this) {
|
|
||||||
self::VALID_SINCE => 's',
|
|
||||||
self::VALID_UNTIL => 'u',
|
|
||||||
self::MAX_VISITS => 'm',
|
|
||||||
self::TITLE => null,
|
|
||||||
self::CRAWLABLE => 'r',
|
|
||||||
self::NO_FORWARD_QUERY => 'w',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public function wasProvided(InputInterface $input): bool
|
|
||||||
{
|
|
||||||
$option = sprintf('--%s', $this->value);
|
|
||||||
$shortcut = $this->shortcut();
|
|
||||||
|
|
||||||
return $input->hasParameterOption($shortcut === null ? $option : [$option, sprintf('-%s', $shortcut)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Input;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
|
|
||||||
use function array_unique;
|
|
||||||
|
|
||||||
readonly class TagsOption
|
|
||||||
{
|
|
||||||
public function __construct(Command $command, string $description)
|
|
||||||
{
|
|
||||||
$command
|
|
||||||
->addOption(
|
|
||||||
'tag',
|
|
||||||
't',
|
|
||||||
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
|
||||||
$description,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether tags have been set or not, via `--tag` or `-t`
|
|
||||||
*/
|
|
||||||
public function exists(InputInterface $input): bool
|
|
||||||
{
|
|
||||||
return $input->hasParameterOption(['--tag', '-t']);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string[]
|
|
||||||
*/
|
|
||||||
public function get(InputInterface $input): array
|
|
||||||
{
|
|
||||||
return array_unique($input->getOption('tag'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ class EditShortUrlCommandTest extends TestCase
|
|||||||
);
|
);
|
||||||
$this->stringifier->expects($this->once())->method('stringify')->willReturn('https://s.test/foo');
|
$this->stringifier->expects($this->once())->method('stringify')->willReturn('https://s.test/foo');
|
||||||
|
|
||||||
$this->commandTester->execute(['shortCode' => 'foobar']);
|
$this->commandTester->execute(['short-code' => 'foobar']);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$exitCode = $this->commandTester->getStatusCode();
|
$exitCode = $this->commandTester->getStatusCode();
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class EditShortUrlCommandTest extends TestCase
|
|||||||
$this->shortUrlService->expects($this->once())->method('updateShortUrl')->willThrowException($e);
|
$this->shortUrlService->expects($this->once())->method('updateShortUrl')->willThrowException($e);
|
||||||
$this->stringifier->expects($this->never())->method('stringify');
|
$this->stringifier->expects($this->never())->method('stringify');
|
||||||
|
|
||||||
$this->commandTester->execute(['shortCode' => 'foo'], ['verbosity' => $verbosity]);
|
$this->commandTester->execute(['short-code' => 'foo'], ['verbosity' => $verbosity]);
|
||||||
$output = $this->commandTester->getDisplay();
|
$output = $this->commandTester->getDisplay();
|
||||||
$exitCode = $this->commandTester->getStatusCode();
|
$exitCode = $this->commandTester->getStatusCode();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user