diff --git a/CHANGELOG.md b/CHANGELOG.md index dc640a3..782f4d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com), and this project adheres to [Semantic Versioning](https://semver.org). +## [4.0.0] - 2022-08-06 +### Added +* *Nothing* + +### Changed +* Used PHP 8.1 syntax wherever possible (readonly, enums, etc). + +### Deprecated +* *Nothing* + +### Removed +* Dropped support for PHP 8.0 + +### Fixed +* *Nothing* + + ## [3.0.0] - 2022-04-23 ### Added * [#47](https://github.com/shlinkio/shlink-importer/issues/47) Added support to import from YOURLS under a specific domain. diff --git a/composer.json b/composer.json index 8abb480..0235887 100644 --- a/composer.json +++ b/composer.json @@ -12,24 +12,24 @@ } ], "require": { - "php": "^8.0", + "php": "^8.1", "ext-json": "*", - "laminas/laminas-servicemanager": "^3.11.2", + "laminas/laminas-servicemanager": "^3.16", "league/csv": "^9.8", "lstrojny/functional-php": "^1.17", - "shlinkio/shlink-config": "^1.6", - "symfony/console": "^6.0" + "shlinkio/shlink-config": "^2.0", + "symfony/console": "^6.1" }, "require-dev": { "guzzlehttp/guzzle": "^7.4", "infection/infection": "^0.26", "phpspec/prophecy-phpunit": "^2.0", - "phpstan/phpstan": "^1.5", + "phpstan/phpstan": "^1.8", "phpunit/phpunit": "^9.5", "psr/http-factory": "^1.0", "roave/security-advisories": "dev-master", "shlinkio/php-coding-standard": "~2.3.0", - "symfony/var-dumper": "^6.0" + "symfony/var-dumper": "^6.1" }, "suggest": { "psr/http-client": "If you want to be able to import URLs from Bit.ly, YOURLS, Kutt.it or another Shlink instance", diff --git a/config/dependencies.config.php b/config/dependencies.config.php index 8d96923..6b92f26 100644 --- a/config/dependencies.config.php +++ b/config/dependencies.config.php @@ -42,11 +42,11 @@ ], 'aliases' => [ - Sources\ImportSources::BITLY => Sources\Bitly\BitlyApiImporter::class, - Sources\ImportSources::CSV => Sources\Csv\CsvImporter::class, - Sources\ImportSources::SHLINK => Sources\Shlink\ShlinkImporter::class, - Sources\ImportSources::YOURLS => Sources\Yourls\YourlsImporter::class, - Sources\ImportSources::KUTT => Sources\Kutt\KuttImporter::class, + Sources\ImportSource::BITLY->value => Sources\Bitly\BitlyApiImporter::class, + Sources\ImportSource::CSV->value => Sources\Csv\CsvImporter::class, + Sources\ImportSource::SHLINK->value => Sources\Shlink\ShlinkImporter::class, + Sources\ImportSource::YOURLS->value => Sources\Yourls\YourlsImporter::class, + Sources\ImportSource::KUTT->value => Sources\Kutt\KuttImporter::class, ], ], @@ -60,11 +60,11 @@ ], 'aliases' => [ - Sources\ImportSources::BITLY => Sources\Bitly\BitlyApiParamsConsoleHelper::class, - Sources\ImportSources::CSV => Sources\Csv\CsvParamsConsoleHelper::class, - Sources\ImportSources::SHLINK => Sources\Shlink\ShlinkParamsConsoleHelper::class, - Sources\ImportSources::YOURLS => Sources\Yourls\YourlsParamsConsoleHelper::class, - Sources\ImportSources::KUTT => Sources\Kutt\KuttParamsConsoleHelper::class, + Sources\ImportSource::BITLY->value => Sources\Bitly\BitlyApiParamsConsoleHelper::class, + Sources\ImportSource::CSV->value => Sources\Csv\CsvParamsConsoleHelper::class, + Sources\ImportSource::SHLINK->value => Sources\Shlink\ShlinkParamsConsoleHelper::class, + Sources\ImportSource::YOURLS->value => Sources\Yourls\YourlsParamsConsoleHelper::class, + Sources\ImportSource::KUTT->value => Sources\Kutt\KuttParamsConsoleHelper::class, ], ], ], diff --git a/src/Command/ImportCommand.php b/src/Command/ImportCommand.php index 04c890f..66e7605 100644 --- a/src/Command/ImportCommand.php +++ b/src/Command/ImportCommand.php @@ -9,8 +9,7 @@ use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\Importer\Params\ConsoleHelper\ConsoleHelperManagerInterface; use Shlinkio\Shlink\Importer\Params\ConsoleHelper\ParamsConsoleHelperInterface; -use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyManagerInterface; use Symfony\Component\Console\Command\Command; @@ -19,7 +18,6 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; -use function Functional\contains; use function implode; use function sprintf; @@ -30,11 +28,11 @@ class ImportCommand extends Command private array $validSources; public function __construct( - private ImporterStrategyManagerInterface $importerStrategyManager, - private ConsoleHelperManagerInterface $consoleHelperManager, - private ImportedLinksProcessorInterface $importedLinksProcessor, + private readonly ImporterStrategyManagerInterface $importerStrategyManager, + private readonly ConsoleHelperManagerInterface $consoleHelperManager, + private readonly ImportedLinksProcessorInterface $importedLinksProcessor, ) { - $this->validSources = ImportSources::getAll(); + $this->validSources = ImportSource::values(); parent::__construct(); } @@ -49,14 +47,6 @@ protected function configure(): void )); } - protected function initialize(InputInterface $input, OutputInterface $output): void - { - $source = $input->getArgument('source'); - if ($source !== null && ! contains($this->validSources, $source)) { - throw InvalidSourceException::fromInvalidSource($source); - } - } - protected function interact(InputInterface $input, OutputInterface $output): void { $source = $input->getArgument('source'); @@ -70,18 +60,26 @@ protected function interact(InputInterface $input, OutputInterface $output): voi } } + protected function initialize(InputInterface $input, OutputInterface $output): void + { + $source = $input->getArgument('source'); + if ($source !== null && ImportSource::tryFrom($source) === null) { + throw InvalidSourceException::fromInvalidSource($source); + } + } + protected function execute(InputInterface $input, OutputInterface $output): ?int { $io = new SymfonyStyle($input, $output); - $source = $input->getArgument('source'); + $source = ImportSource::from($input->getArgument('source')); /** @var ParamsConsoleHelperInterface $paramsHelper */ - $paramsHelper = $this->consoleHelperManager->get($source); + $paramsHelper = $this->consoleHelperManager->get($source->value); /** @var ImporterStrategyInterface $importerStrategy */ - $importerStrategy = $this->importerStrategyManager->get($source); + $importerStrategy = $this->importerStrategyManager->get($source->value); try { - $params = ImportParams::fromSourceAndCallableMap($source, $paramsHelper->requestParams($io)); + $params = $source->toParamsWithCallableMap($paramsHelper->requestParams($io)); $links = $importerStrategy->import($params); $this->importedLinksProcessor->process($io, $links, $params); } catch (ImportException $e) { @@ -94,7 +92,7 @@ protected function execute(InputInterface $input, OutputInterface $output): ?int private function handleImportError(ImportException $e, SymfonyStyle $io): void { - $continueToken = $e->continueToken(); + $continueToken = $e->continueToken; if ($continueToken === null) { $io->error('An error occurred while importing URLs.'); diff --git a/src/Exception/ImportException.php b/src/Exception/ImportException.php index 9683265..bd89aae 100644 --- a/src/Exception/ImportException.php +++ b/src/Exception/ImportException.php @@ -11,7 +11,7 @@ class ImportException extends RuntimeException implements ExceptionInterface { protected function __construct( string $message, - private ?string $continueToken, + public readonly ?string $continueToken, int $code = 0, ?Throwable $previous = null, ) { @@ -22,9 +22,4 @@ public static function fromError(Throwable $e): self { return new self('An error occurred while importing URLs', null, -1, $e); } - - public function continueToken(): ?string - { - return $this->continueToken; - } } diff --git a/src/Exception/InvalidSourceException.php b/src/Exception/InvalidSourceException.php index d551dae..ab89ede 100644 --- a/src/Exception/InvalidSourceException.php +++ b/src/Exception/InvalidSourceException.php @@ -5,7 +5,7 @@ namespace Shlinkio\Shlink\Importer\Exception; use InvalidArgumentException; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use function implode; use function sprintf; @@ -17,7 +17,7 @@ public static function fromInvalidSource(string $source): self return new self(sprintf( 'Provided source "%s" is not valid. Expected one of ["%s"]', $source, - implode('", "', ImportSources::getAll()), + implode('", "', ImportSource::values()), )); } } diff --git a/src/Http/InvalidRequestException.php b/src/Http/InvalidRequestException.php index 715cd9d..cd84781 100644 --- a/src/Http/InvalidRequestException.php +++ b/src/Http/InvalidRequestException.php @@ -8,11 +8,16 @@ use Shlinkio\Shlink\Importer\Exception\ExceptionInterface; use function sprintf; +use function str_contains; class InvalidRequestException extends RuntimeException implements ExceptionInterface { - private function __construct(string $message, private string $url, private int $statusCode, private string $body) - { + private function __construct( + string $message, + public readonly string $url, + public readonly int $statusCode, + public readonly string $body, + ) { parent::__construct($message); } @@ -26,18 +31,8 @@ public static function fromResponseData(string $url, int $statusCode, string $bo ); } - public function url(): string - { - return $this->url; - } - - public function statusCode(): int - { - return $this->statusCode; - } - - public function body(): string + public function isShlinkPluginMissingError(): bool { - return $this->body; + return str_contains($this->body, '"message":"Unknown or missing \"action\" parameter"'); } } diff --git a/src/Http/RestApiConsumer.php b/src/Http/RestApiConsumer.php index c4a7f6c..5e20a03 100644 --- a/src/Http/RestApiConsumer.php +++ b/src/Http/RestApiConsumer.php @@ -15,8 +15,10 @@ class RestApiConsumer implements RestApiConsumerInterface { - public function __construct(private ClientInterface $httpClient, private RequestFactoryInterface $requestFactory) - { + public function __construct( + private readonly ClientInterface $httpClient, + private readonly RequestFactoryInterface $requestFactory, + ) { } /** diff --git a/src/Model/ImportedShlinkUrl.php b/src/Model/ImportedShlinkUrl.php index 45716d5..2adb4ee 100644 --- a/src/Model/ImportedShlinkUrl.php +++ b/src/Model/ImportedShlinkUrl.php @@ -5,6 +5,7 @@ namespace Shlinkio\Shlink\Importer\Model; use DateTimeInterface; +use Shlinkio\Shlink\Importer\Sources\ImportSource; class ImportedShlinkUrl { @@ -12,70 +13,16 @@ class ImportedShlinkUrl * @param iterable $visits */ public function __construct( - private string $source, - private string $longUrl, - private array $tags, - private DateTimeInterface $createdAt, - private ?string $domain, - private string $shortCode, - private ?string $title, - private iterable $visits = [], - private ?int $visitsCount = null, - private ?ImportedShlinkUrlMeta $meta = null, + public readonly ImportSource $source, + public readonly string $longUrl, + public readonly array $tags, + public readonly DateTimeInterface $createdAt, + public readonly ?string $domain, + public readonly string $shortCode, + public readonly ?string $title, + public readonly iterable $visits = [], + public readonly ?int $visitsCount = null, + public ImportedShlinkUrlMeta $meta = new ImportedShlinkUrlMeta(), ) { } - - public function source(): string - { - return $this->source; - } - - public function longUrl(): string - { - return $this->longUrl; - } - - public function tags(): array - { - return $this->tags; - } - - public function createdAt(): DateTimeInterface - { - return $this->createdAt; - } - - public function domain(): ?string - { - return $this->domain; - } - - public function shortCode(): string - { - return $this->shortCode; - } - - public function title(): ?string - { - return $this->title; - } - - /** - * @return iterable - */ - public function visits(): iterable - { - return $this->visits; - } - - public function visitsCount(): ?int - { - return $this->visitsCount; - } - - public function meta(): ImportedShlinkUrlMeta - { - $this->meta = $this->meta ?? ImportedShlinkUrlMeta::emptyInstance(); - return $this->meta; - } } diff --git a/src/Model/ImportedShlinkUrlMeta.php b/src/Model/ImportedShlinkUrlMeta.php index 26fd287..e2bbf43 100644 --- a/src/Model/ImportedShlinkUrlMeta.php +++ b/src/Model/ImportedShlinkUrlMeta.php @@ -9,29 +9,9 @@ class ImportedShlinkUrlMeta { public function __construct( - private ?DateTimeInterface $validSince, - private ?DateTimeInterface $validUntil, - private ?int $maxVisits, + public readonly ?DateTimeInterface $validSince = null, + public readonly ?DateTimeInterface $validUntil = null, + public readonly ?int $maxVisits = null, ) { } - - public static function emptyInstance(): self - { - return new self(null, null, null); - } - - public function validSince(): ?DateTimeInterface - { - return $this->validSince; - } - - public function validUntil(): ?DateTimeInterface - { - return $this->validUntil; - } - - public function maxVisits(): ?int - { - return $this->maxVisits; - } } diff --git a/src/Model/ImportedShlinkVisit.php b/src/Model/ImportedShlinkVisit.php index 3839b3c..27c0f2d 100644 --- a/src/Model/ImportedShlinkVisit.php +++ b/src/Model/ImportedShlinkVisit.php @@ -9,30 +9,10 @@ class ImportedShlinkVisit { public function __construct( - private string $referer, - private string $userAgent, - private DateTimeInterface $date, - private ?ImportedShlinkVisitLocation $location, + public readonly string $referer, + public readonly string $userAgent, + public readonly DateTimeInterface $date, + public readonly ?ImportedShlinkVisitLocation $location, ) { } - - public function referer(): string - { - return $this->referer; - } - - public function userAgent(): string - { - return $this->userAgent; - } - - public function date(): DateTimeInterface - { - return $this->date; - } - - public function location(): ?ImportedShlinkVisitLocation - { - return $this->location; - } } diff --git a/src/Model/ImportedShlinkVisitLocation.php b/src/Model/ImportedShlinkVisitLocation.php index bc9c613..024f7d2 100644 --- a/src/Model/ImportedShlinkVisitLocation.php +++ b/src/Model/ImportedShlinkVisitLocation.php @@ -7,48 +7,13 @@ class ImportedShlinkVisitLocation { public function __construct( - private string $countryCode, - private string $countryName, - private string $regionName, - private string $cityName, - private string $timezone, - private float $latitude, - private float $longitude, + public readonly string $countryCode, + public readonly string $countryName, + public readonly string $regionName, + public readonly string $cityName, + public readonly string $timezone, + public readonly float $latitude, + public readonly float $longitude, ) { } - - public function countryCode(): string - { - return $this->countryCode; - } - - public function countryName(): string - { - return $this->countryName; - } - - public function regionName(): string - { - return $this->regionName; - } - - public function cityName(): string - { - return $this->cityName; - } - - public function timezone(): string - { - return $this->timezone; - } - - public function latitude(): float - { - return $this->latitude; - } - - public function longitude(): float - { - return $this->longitude; - } } diff --git a/src/Params/ImportParams.php b/src/Params/ImportParams.php index a6611bd..bcffbd4 100644 --- a/src/Params/ImportParams.php +++ b/src/Params/ImportParams.php @@ -4,23 +4,25 @@ namespace Shlinkio\Shlink\Importer\Params; +use Shlinkio\Shlink\Importer\Sources\ImportSource; + final class ImportParams { public const IMPORT_SHORT_CODES_PARAM = 'import_short_codes'; public const IMPORT_VISITS_PARAM = 'import_visits'; private function __construct( - private string $source, - private bool $importShortCodes, - private bool $importVisits, - private array $extraParams, + public readonly ImportSource $source, + public readonly bool $importShortCodes, + public readonly bool $importVisits, + private readonly array $extraParams, ) { } /** * @param array $callableMap */ - public static function fromSourceAndCallableMap(string $source, array $callableMap): self + public static function fromSourceAndCallableMap(ImportSource $source, array $callableMap): self { $importShortCodes = self::extractParamWithDefault($callableMap, self::IMPORT_SHORT_CODES_PARAM, true); $importVisits = self::extractParamWithDefault($callableMap, self::IMPORT_VISITS_PARAM, false); @@ -33,7 +35,7 @@ public static function fromSourceAndCallableMap(string $source, array $callableM ); } - public static function fromSource(string $source): self + public static function fromSource(ImportSource $source): self { return new self($source, true, false, []); } @@ -46,21 +48,6 @@ private static function extractParamWithDefault(array &$callableMap, string $key return $extracted; } - public function source(): string - { - return $this->source; - } - - public function importShortCodes(): bool - { - return $this->importShortCodes; - } - - public function importVisits(): bool - { - return $this->importVisits; - } - public function extraParam(string $key): mixed { return $this->extraParams[$key] ?? null; diff --git a/src/Sources/Bitly/BitlyApiException.php b/src/Sources/Bitly/BitlyApiException.php index ea3a6ef..0704c04 100644 --- a/src/Sources/Bitly/BitlyApiException.php +++ b/src/Sources/Bitly/BitlyApiException.php @@ -15,9 +15,9 @@ public static function fromInvalidRequest(InvalidRequestException $e, ?string $c { return new self(sprintf( 'Request to Bitly API v4 to URL "%s" failed with status code "%s" and body "%s"', - $e->url(), - $e->statusCode(), - $e->body(), + $e->url, + $e->statusCode, + $e->body, ), $continueToken); } } diff --git a/src/Sources/Bitly/BitlyApiImporter.php b/src/Sources/Bitly/BitlyApiImporter.php index 6aa1bf5..799cd02 100644 --- a/src/Sources/Bitly/BitlyApiImporter.php +++ b/src/Sources/Bitly/BitlyApiImporter.php @@ -11,7 +11,7 @@ use Shlinkio\Shlink\Importer\Http\RestApiConsumerInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use Shlinkio\Shlink\Importer\Util\DateHelper; use Throwable; @@ -26,7 +26,7 @@ class BitlyApiImporter implements ImporterStrategyInterface { - public function __construct(private RestApiConsumerInterface $apiConsumer) + public function __construct(private readonly RestApiConsumerInterface $apiConsumer) { } @@ -71,7 +71,7 @@ private function loadUrlsForGroup( BitlyApiProgressTracker $progressTracker, ): iterable { $pagination = []; - $archived = $params->ignoreArchived() ? 'off' : 'both'; + $archived = $params->ignoreArchived ? 'off' : 'both'; $createdBefore = $groupId === $progressTracker->initialGroup() ? $progressTracker->createdBefore() : ''; do { @@ -96,17 +96,17 @@ private function loadUrlsForGroup( } $longUrl = $link['long_url']; - $date = $hasCreatedDate && $params->keepCreationDate() + $date = $hasCreatedDate && $params->keepCreationDate ? DateHelper::dateFromAtom($link['created_at']) : clone $progressTracker->startDate(); $parsedLink = $this->parseLink($link['link'] ?? ''); $host = $parsedLink['host'] ?? null; - $domain = $host !== 'bit.ly' && $params->importCustomDomains() ? $host : null; + $domain = $host !== 'bit.ly' && $params->importCustomDomains ? $host : null; $shortCode = ltrim($parsedLink['path'] ?? '', '/'); - $tags = $params->importTags() ? $link['tags'] ?? [] : []; + $tags = $params->importTags ? $link['tags'] ?? [] : []; $title = $link['title'] ?? null; - return new ImportedShlinkUrl(ImportSources::BITLY, $longUrl, $tags, $date, $domain, $shortCode, $title); + return new ImportedShlinkUrl(ImportSource::BITLY, $longUrl, $tags, $date, $domain, $shortCode, $title); }); } while (! empty($pagination['next'])); } @@ -123,11 +123,11 @@ private function callToBitlyApi( $url = str_starts_with($url, 'http') ? $url : sprintf('https://api-ssl.bitly.com/v4%s', $url); try { - return $this->apiConsumer->callApi($url, ['Authorization' => sprintf('Bearer %s', $params->accessToken())]); + return $this->apiConsumer->callApi($url, ['Authorization' => sprintf('Bearer %s', $params->accessToken)]); } catch (InvalidRequestException $e) { throw BitlyApiException::fromInvalidRequest( $e, - $progressTracker->generateContinueToken() ?? $params->continueToken(), + $progressTracker->generateContinueToken() ?? $params->continueToken, ); } } diff --git a/src/Sources/Bitly/BitlyApiParams.php b/src/Sources/Bitly/BitlyApiParams.php index a796563..734c2bf 100644 --- a/src/Sources/Bitly/BitlyApiParams.php +++ b/src/Sources/Bitly/BitlyApiParams.php @@ -8,58 +8,25 @@ final class BitlyApiParams { - private string $accessToken; - private bool $importTags; - private bool $importCustomDomains; - private bool $keepCreationDate; - private bool $ignoreArchived; - private ?string $continueToken = null; - - private function __construct() - { + private function __construct( + public readonly string $accessToken, + public readonly bool $importTags, + public readonly bool $importCustomDomains, + public readonly bool $keepCreationDate, + public readonly bool $ignoreArchived, + public readonly ?string $continueToken = null, + ) { } public static function fromImportParams(ImportParams $params): self { - $instance = new self(); - - $instance->accessToken = $params->extraParam('access_token'); - $instance->importTags = (bool) ($params->extraParam('import_tags') ?? true); - $instance->importCustomDomains = (bool) ($params->extraParam('import_custom_domains') ?? false); - $instance->keepCreationDate = (bool) ($params->extraParam('keep_creation_date') ?? true); - $instance->ignoreArchived = (bool) ($params->extraParam('ignore_archived') ?? false); - $instance->continueToken = $params->extraParam('continue_token'); - - return $instance; - } - - public function accessToken(): string - { - return $this->accessToken; - } - - public function importTags(): bool - { - return $this->importTags; - } - - public function importCustomDomains(): bool - { - return $this->importCustomDomains; - } - - public function keepCreationDate(): bool - { - return $this->keepCreationDate; - } - - public function ignoreArchived(): bool - { - return $this->ignoreArchived; - } - - public function continueToken(): ?string - { - return $this->continueToken; + return new self( + $params->extraParam('access_token'), + (bool) ($params->extraParam('import_tags') ?? true), + (bool) ($params->extraParam('import_custom_domains') ?? false), + (bool) ($params->extraParam('keep_creation_date') ?? true), + (bool) ($params->extraParam('ignore_archived') ?? false), + $params->extraParam('continue_token'), + ); } } diff --git a/src/Sources/Bitly/BitlyApiProgressTracker.php b/src/Sources/Bitly/BitlyApiProgressTracker.php index f9e2b30..450772c 100644 --- a/src/Sources/Bitly/BitlyApiProgressTracker.php +++ b/src/Sources/Bitly/BitlyApiProgressTracker.php @@ -29,7 +29,7 @@ private function __construct() public static function initFromParams(BitlyApiParams $params): self { - $providedContinueToken = $params->continueToken(); + $providedContinueToken = $params->continueToken; $instance = new self(); if ($providedContinueToken === null) { return $instance; diff --git a/src/Sources/Csv/CsvImporter.php b/src/Sources/Csv/CsvImporter.php index b5833f1..102baa9 100644 --- a/src/Sources/Csv/CsvImporter.php +++ b/src/Sources/Csv/CsvImporter.php @@ -10,7 +10,7 @@ use Shlinkio\Shlink\Importer\Exception\ImportException; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use function array_filter; @@ -24,7 +24,7 @@ class CsvImporter implements ImporterStrategyInterface { private const TAG_SEPARATOR = '|'; - public function __construct(private ?DateTimeInterface $date = null) + public function __construct(private readonly ?DateTimeInterface $date = null) { } @@ -37,14 +37,14 @@ public function import(ImportParams $importParams): iterable $params = CsvParams::fromImportParams($importParams); $now = $this->date ?? new DateTimeImmutable(); - $csvReader = Reader::createFromStream($params->stream())->setDelimiter($params->delimiter()) - ->setHeaderOffset(0); + $csvReader = Reader::createFromStream($params->stream)->setDelimiter($params->delimiter) + ->setHeaderOffset(0); foreach ($csvReader as $record) { $record = $this->remapRecordHeaders($record); yield new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, $record['longurl'], $this->parseTags($record), $now, diff --git a/src/Sources/Csv/CsvParams.php b/src/Sources/Csv/CsvParams.php index a2fa2b5..272f758 100644 --- a/src/Sources/Csv/CsvParams.php +++ b/src/Sources/Csv/CsvParams.php @@ -8,33 +8,20 @@ final class CsvParams { - /** @var resource */ - private $stream; - private string $delimiter; - - private function __construct() - { - } - - public static function fromImportParams(ImportParams $params): self - { - $instance = new self(); - $instance->delimiter = $params->extraParam('delimiter') ?? ''; - $instance->stream = $params->extraParam('stream') ?? ''; - - return $instance; - } - /** - * @return resource + * @param resource $stream */ - public function stream() - { - return $this->stream; + private function __construct( + public readonly mixed $stream, + public readonly string $delimiter, + ) { } - public function delimiter(): string + public static function fromImportParams(ImportParams $params): self { - return $this->delimiter; + return new self( + $params->extraParam('stream') ?? '', + $params->extraParam('delimiter') ?? '', + ); } } diff --git a/src/Sources/Csv/CsvParamsConsoleHelper.php b/src/Sources/Csv/CsvParamsConsoleHelper.php index 995a7a0..c9578ee 100644 --- a/src/Sources/Csv/CsvParamsConsoleHelper.php +++ b/src/Sources/Csv/CsvParamsConsoleHelper.php @@ -19,7 +19,7 @@ public function requestParams(StyleInterface $io): array { return [ 'stream' => fn () => - $io->ask('What\'s the path for the CSV file you want to import', null, [$this, 'pathToStream']), + $io->ask('What\'s the path for the CSV file you want to import', null, $this->pathToStream(...)), 'delimiter' => fn () => $io->choice('What\'s the delimiter used to separate values?', [ ',' => 'Comma', ';' => 'Semicolon', diff --git a/src/Sources/ImportSource.php b/src/Sources/ImportSource.php new file mode 100644 index 0000000..45f5093 --- /dev/null +++ b/src/Sources/ImportSource.php @@ -0,0 +1,36 @@ + $callableMap + */ + public function toParamsWithCallableMap(array $callableMap): ImportParams + { + return ImportParams::fromSourceAndCallableMap($this, $callableMap); + } + + public static function values(): array + { + return map(self::cases(), static fn (ImportSource $source) => $source->value); + } +} diff --git a/src/Sources/ImportSources.php b/src/Sources/ImportSources.php deleted file mode 100644 index f96a39a..0000000 --- a/src/Sources/ImportSources.php +++ /dev/null @@ -1,23 +0,0 @@ - self::SHORT_URLS_PER_PAGE, 'skip' => $skip, - 'all' => $params->importAllUrls() ? 'true' : 'false', + 'all' => $params->importAllUrls ? 'true' : 'false', ]); ['data' => $urls, 'total' => $total] = $this->apiConsumer->callApi( - sprintf('%s/api/v2/links?%s', $params->baseUrl(), $queryString), + sprintf('%s/api/v2/links?%s', $params->baseUrl, $queryString), [ - 'X-Api-Key' => $params->apiKey(), + 'X-Api-Key' => $params->apiKey, 'Accept' => 'application/json', ], ); @@ -74,7 +74,7 @@ private function mapUrls(array $urls): array $visitsCount = $url['visit_count']; return new ImportedShlinkUrl( - ImportSources::KUTT, + ImportSource::KUTT, $url['target'], [], new DateTimeImmutable($url['created_at']), diff --git a/src/Sources/Kutt/KuttParams.php b/src/Sources/Kutt/KuttParams.php index 1f39df4..583db23 100644 --- a/src/Sources/Kutt/KuttParams.php +++ b/src/Sources/Kutt/KuttParams.php @@ -9,9 +9,9 @@ final class KuttParams { private function __construct( - private string $baseUrl, - private string $apiKey, - private bool $importAllUrls, + public readonly string $baseUrl, + public readonly string $apiKey, + public readonly bool $importAllUrls, ) { } @@ -23,19 +23,4 @@ public static function fromImportParams(ImportParams $params): self $params->extraParam('import_all_urls') ?? false, ); } - - public function baseUrl(): string - { - return $this->baseUrl; - } - - public function apiKey(): string - { - return $this->apiKey; - } - - public function importAllUrls(): bool - { - return $this->importAllUrls; - } } diff --git a/src/Sources/Shlink/ShlinkImporter.php b/src/Sources/Shlink/ShlinkImporter.php index a75710c..dea9e83 100644 --- a/src/Sources/Shlink/ShlinkImporter.php +++ b/src/Sources/Shlink/ShlinkImporter.php @@ -16,7 +16,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisitLocation; use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use Shlinkio\Shlink\Importer\Util\DateHelper; use Throwable; @@ -34,7 +34,7 @@ class ShlinkImporter implements ImporterStrategyInterface private DateTimeImmutable $importStartTime; - public function __construct(private RestApiConsumerInterface $apiConsumer) + public function __construct(private readonly RestApiConsumerInterface $apiConsumer) { } @@ -61,10 +61,10 @@ public function import(ImportParams $importParams): iterable private function loadUrls(ShlinkParams $params, int $page = 1): Generator { $queryString = http_build_query(['page' => $page, 'itemsPerPage' => self::SHORT_URLS_PER_PAGE]); - $url = sprintf('%s/rest/v2/short-urls?%s', $params->baseUrl(), $queryString); + $url = sprintf('%s/rest/v2/short-urls?%s', $params->baseUrl, $queryString); $parsedBody = $this->apiConsumer->callApi( $url, - ['X-Api-Key' => $params->apiKey(), 'Accept' => 'application/json'], + ['X-Api-Key' => $params->apiKey, 'Accept' => 'application/json'], ); yield from $this->mapUrls($parsedBody['shortUrls']['data'] ?? [], $params); @@ -100,14 +100,14 @@ private function mapUrls(array $urls, ShlinkParams $params): array ); return new ImportedShlinkUrl( - ImportSources::SHLINK, + ImportSource::SHLINK, $url['longUrl'] ?? '', $url['tags'] ?? [], DateHelper::nullableDateFromAtom($url['dateCreated'] ?? null) ?? $this->importStartTime, $domain, $shortCode, $url['title'] ?? null, - $params->importVisits() && $expectedPages > 0 + $params->importVisits && $expectedPages > 0 ? $this->loadVisits($shortCode, $domain, $params, $expectedPages) : [], $visitsCount, @@ -126,10 +126,10 @@ private function loadVisits(string $shortCode, ?string $domain, ShlinkParams $pa $queryString = http_build_query( ['page' => $page, 'itemsPerPage' => self::VISITS_PER_PAGE, 'domain' => $domain], ); - $url = sprintf('%s/rest/v2/short-urls/%s/visits?%s', $params->baseUrl(), $shortCode, $queryString); + $url = sprintf('%s/rest/v2/short-urls/%s/visits?%s', $params->baseUrl, $shortCode, $queryString); $parsedBody = $this->apiConsumer->callApi( $url, - ['X-Api-Key' => $params->apiKey(), 'Accept' => 'application/json'], + ['X-Api-Key' => $params->apiKey, 'Accept' => 'application/json'], ); yield from array_reverse($this->mapVisits($parsedBody['visits']['data'] ?? [])); diff --git a/src/Sources/Shlink/ShlinkParams.php b/src/Sources/Shlink/ShlinkParams.php index 7796af8..adc65bd 100644 --- a/src/Sources/Shlink/ShlinkParams.php +++ b/src/Sources/Shlink/ShlinkParams.php @@ -8,8 +8,11 @@ final class ShlinkParams { - private function __construct(private string $baseUrl, private string $apiKey, private bool $importVisits) - { + private function __construct( + public readonly string $baseUrl, + public readonly string $apiKey, + public readonly bool $importVisits, + ) { } public static function fromImportParams(ImportParams $params): self @@ -17,22 +20,7 @@ public static function fromImportParams(ImportParams $params): self return new self( $params->extraParam('base_url') ?? '', $params->extraParam('api_key') ?? '', - $params->importVisits(), + $params->importVisits, ); } - - public function baseUrl(): string - { - return $this->baseUrl; - } - - public function apiKey(): string - { - return $this->apiKey; - } - - public function importVisits(): bool - { - return $this->importVisits; - } } diff --git a/src/Sources/Yourls/YourlsImporter.php b/src/Sources/Yourls/YourlsImporter.php index 5bd3af8..a5ff0c2 100644 --- a/src/Sources/Yourls/YourlsImporter.php +++ b/src/Sources/Yourls/YourlsImporter.php @@ -12,7 +12,7 @@ use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisit; use Shlinkio\Shlink\Importer\Model\ImportedShlinkVisitLocation; use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use Shlinkio\Shlink\Importer\Util\DateHelper; use Throwable; @@ -20,7 +20,6 @@ use function Functional\map; use function http_build_query; use function sprintf; -use function str_contains; class YourlsImporter implements ImporterStrategyInterface { @@ -28,7 +27,7 @@ class YourlsImporter implements ImporterStrategyInterface private const VISITS_ACTION = 'shlink-link-visits'; private const YOURLS_DATE_FORMAT = 'Y-m-d H:i:s'; - public function __construct(private RestApiConsumerInterface $apiConsumer) + public function __construct(private readonly RestApiConsumerInterface $apiConsumer) { } @@ -41,7 +40,7 @@ public function import(ImportParams $importParams): iterable try { yield from $this->loadUrls(YourlsParams::fromImportParams($importParams)); } catch (InvalidRequestException $e) { - if (str_contains($e->body(), '"message":"Unknown or missing \"action\" parameter"')) { + if ($e->isShlinkPluginMissingError()) { throw YourlsMissingPluginException::forMissingPlugin($e); } @@ -59,14 +58,14 @@ private function loadUrls(YourlsParams $params): Generator $shortCode = $url['keyword'] ?? ''; return new ImportedShlinkUrl( - ImportSources::YOURLS, + ImportSource::YOURLS, $url['url'] ?? '', [], DateHelper::dateFromFormat(self::YOURLS_DATE_FORMAT, $url['timestamp'] ?? ''), - $params->domain(), + $params->domain, $shortCode, $url['title'] ?? null, - $params->importVisits() ? $this->loadVisits($shortCode, $params) : [], + $params->importVisits ? $this->loadVisits($shortCode, $params) : [], (int) ($url['clicks'] ?? 0), ); }); @@ -94,10 +93,10 @@ private function callYourlsApi(string $action, YourlsParams $params, string $sho 'format' => 'json', 'action' => $action, 'shortCode' => $shortCode, - 'username' => $params->username(), - 'password' => $params->password(), + 'username' => $params->username, + 'password' => $params->password, ]); - $resp = $this->apiConsumer->callApi(sprintf('%s/yourls-api.php?%s', $params->baseUrl(), $query)); + $resp = $this->apiConsumer->callApi(sprintf('%s/yourls-api.php?%s', $params->baseUrl, $query)); return $resp['result'] ?? []; } diff --git a/src/Sources/Yourls/YourlsParams.php b/src/Sources/Yourls/YourlsParams.php index 9ccf998..931f083 100644 --- a/src/Sources/Yourls/YourlsParams.php +++ b/src/Sources/Yourls/YourlsParams.php @@ -9,11 +9,11 @@ final class YourlsParams { private function __construct( - private string $baseUrl, - private string $username, - private string $password, - private bool $importVisits, - private ?string $domain, + public readonly string $baseUrl, + public readonly string $username, + public readonly string $password, + public readonly bool $importVisits, + public readonly ?string $domain, ) { } @@ -23,33 +23,8 @@ public static function fromImportParams(ImportParams $params): self $params->extraParam('base_url') ?? '', $params->extraParam('username') ?? '', $params->extraParam('password') ?? '', - $params->importVisits(), + $params->importVisits, $params->extraParam('domain'), ); } - - public function baseUrl(): string - { - return $this->baseUrl; - } - - public function username(): string - { - return $this->username; - } - - public function password(): string - { - return $this->password; - } - - public function importVisits(): bool - { - return $this->importVisits; - } - - public function domain(): ?string - { - return $this->domain; - } } diff --git a/test/Command/ImportCommandTest.php b/test/Command/ImportCommandTest.php index 4e1c058..a94266a 100644 --- a/test/Command/ImportCommandTest.php +++ b/test/Command/ImportCommandTest.php @@ -18,7 +18,7 @@ use Shlinkio\Shlink\Importer\Params\ConsoleHelper\ParamsConsoleHelperInterface; use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiException; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyInterface; use Shlinkio\Shlink\Importer\Strategy\ImporterStrategyManagerInterface; use Symfony\Component\Console\Application; @@ -81,8 +81,8 @@ public function exceptionIsThrownWhenInvalidSourceIsProvided(): void */ public function dependenciesAreInvokedAsExpected(?string $providedSource, bool $expectSourceQuestion): void { - $source = $providedSource ?? ImportSources::BITLY; - $params = ImportParams::fromSource($source); + $source = $providedSource ?? ImportSource::BITLY->value; + $params = ImportParams::fromSource(ImportSource::from($source)); $requestParams = $this->paramsHelper->requestParams(Argument::type(StyleInterface::class))->willReturn([]); $import = $this->importerStrategy->import($params)->willReturn([]); @@ -109,7 +109,7 @@ public function dependenciesAreInvokedAsExpected(?string $providedSource, bool $ public function provideSource(): iterable { - yield 'provided source' => [ImportSources::BITLY, false]; + yield 'provided source' => [ImportSource::BITLY->value, false]; yield 'not provided source' => [null, true]; } @@ -124,10 +124,13 @@ public function importErrorsAreProperlyHandled( array $notExpectedOutputs, ): void { $requestParams = $this->paramsHelper->requestParams(Argument::type(StyleInterface::class))->willReturn([]); - $import = $this->importerStrategy->import(ImportParams::fromSource(ImportSources::BITLY))->willThrow($e); + $import = $this->importerStrategy->import(ImportSource::BITLY->toParams())->willThrow($e); $process = $this->importedLinksProcessor->process(Argument::cetera()); - $exitCode = $this->commandTester->execute(['source' => ImportSources::BITLY], ['verbosity' => $verbosity]); + $exitCode = $this->commandTester->execute( + ['source' => ImportSource::BITLY->value], + ['verbosity' => $verbosity], + ); $output = $this->commandTester->getDisplay(); self::assertEquals(ImportCommand::FAILURE, $exitCode); diff --git a/test/Exception/ImportExceptionTest.php b/test/Exception/ImportExceptionTest.php index c0b1025..ac112e1 100644 --- a/test/Exception/ImportExceptionTest.php +++ b/test/Exception/ImportExceptionTest.php @@ -19,6 +19,6 @@ public function fromErrorCreatesExceptionWithPrevious(): void self::assertEquals('An error occurred while importing URLs', $e->getMessage()); self::assertEquals($prev, $e->getPrevious()); self::assertEquals(-1, $e->getCode()); - self::assertNull($e->continueToken()); + self::assertNull($e->continueToken); } } diff --git a/test/Http/InvalidRequestExceptionTest.php b/test/Http/InvalidRequestExceptionTest.php index 0c003bb..e96ff62 100644 --- a/test/Http/InvalidRequestExceptionTest.php +++ b/test/Http/InvalidRequestExceptionTest.php @@ -19,8 +19,8 @@ public function exceptionIsCreatedAsExpected(): void $e = InvalidRequestException::fromResponseData($url, $statusCode, $body); self::assertEquals('Request to foo.com failed with status code 403', $e->getMessage()); - self::assertEquals($url, $e->url()); - self::assertEquals($statusCode, $e->statusCode()); - self::assertEquals($body, $e->body()); + self::assertEquals($url, $e->url); + self::assertEquals($statusCode, $e->statusCode); + self::assertEquals($body, $e->body); } } diff --git a/test/Sources/Bitly/BitlyApiExceptionTest.php b/test/Sources/Bitly/BitlyApiExceptionTest.php index 0161b93..cd91fe4 100644 --- a/test/Sources/Bitly/BitlyApiExceptionTest.php +++ b/test/Sources/Bitly/BitlyApiExceptionTest.php @@ -25,7 +25,7 @@ public function generatesExpectedMessage(?string $continueToken): void 'Request to Bitly API v4 to URL "something.com" failed with status code "500" and body "Error body"', $e->getMessage(), ); - self::assertEquals($continueToken, $e->continueToken()); + self::assertEquals($continueToken, $e->continueToken); } public function provideContinueToken(): iterable diff --git a/test/Sources/Bitly/BitlyApiImporterTest.php b/test/Sources/Bitly/BitlyApiImporterTest.php index 68a1a38..978b22c 100644 --- a/test/Sources/Bitly/BitlyApiImporterTest.php +++ b/test/Sources/Bitly/BitlyApiImporterTest.php @@ -13,10 +13,9 @@ use Shlinkio\Shlink\Importer\Http\InvalidRequestException; use Shlinkio\Shlink\Importer\Http\RestApiConsumerInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl as ShlinkUrl; -use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiException; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiImporter; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use function explode; use function sprintf; @@ -43,7 +42,7 @@ public function setUp(): void public function groupsAndUrlsAreRecursivelyFetched(array $paramsMap, array $expected): void { $paramsMap['access_token'] = static fn () => 'abc123'; - $params = ImportParams::fromSourceAndCallableMap('', $paramsMap); + $params = ImportSource::BITLY->toParamsWithCallableMap($paramsMap); $sendGroupsRequest = $this->apiConsumer->callApi( 'https://api-ssl.bitly.com/v4/groups', @@ -120,7 +119,7 @@ public function groupsAndUrlsAreRecursivelyFetched(array $paramsMap, array $expe public function provideParams(): iterable { - $source = ImportSources::BITLY; + $source = ImportSource::BITLY; yield 'default options' => [[], [ new ShlinkUrl($source, 'https://shlink.io', [], $this->createDate( @@ -243,7 +242,9 @@ public function throwsExceptionWhenStatusCodeReturnedByApiIsError(int $statusCod $this->expectErrorMessage(sprintf('failed with status code "%s" and body "Error"', $statusCode)); $sendRequest->shouldBeCalledOnce(); - $list = $this->importer->import(ImportParams::fromSourceAndCallableMap('', ['access_token' => fn () => 'abc'])); + $list = $this->importer->import( + ImportSource::BITLY->toParamsWithCallableMap(['access_token' => fn () => 'abc']), + ); foreach ($list as $item) { // Iteration needed to trigger generator code } diff --git a/test/Sources/Bitly/BitlyApiParamsTest.php b/test/Sources/Bitly/BitlyApiParamsTest.php index af291d6..8d4ed44 100644 --- a/test/Sources/Bitly/BitlyApiParamsTest.php +++ b/test/Sources/Bitly/BitlyApiParamsTest.php @@ -5,8 +5,8 @@ namespace ShlinkioTest\Shlink\Importer\Sources\Bitly; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiParams; +use Shlinkio\Shlink\Importer\Sources\ImportSource; class BitlyApiParamsTest extends TestCase { @@ -16,7 +16,7 @@ class BitlyApiParamsTest extends TestCase */ public function rawParamsAreProperlyParsed(array $rawParams, callable $runAssertions): void { - $params = BitlyApiParams::fromImportParams(ImportParams::fromSourceAndCallableMap('', $rawParams)); + $params = BitlyApiParams::fromImportParams(ImportSource::BITLY->toParamsWithCallableMap($rawParams)); $runAssertions($params); } @@ -30,12 +30,12 @@ public function provideRawParams(): iterable 'ignore_archived' => fn () => true, 'continue_token' => fn () => null, ], static function (BitlyApiParams $params): void { - self::assertEquals('token', $params->accessToken()); - self::assertTrue($params->importTags()); - self::assertTrue($params->importCustomDomains()); - self::assertTrue($params->keepCreationDate()); - self::assertTrue($params->ignoreArchived()); - self::assertNull($params->continueToken()); + self::assertEquals('token', $params->accessToken); + self::assertTrue($params->importTags); + self::assertTrue($params->importCustomDomains); + self::assertTrue($params->keepCreationDate); + self::assertTrue($params->ignoreArchived); + self::assertNull($params->continueToken); }]; yield [[ 'access_token' => fn () => 'token', @@ -45,20 +45,20 @@ public function provideRawParams(): iterable 'ignore_archived' => fn () => false, 'continue_token' => fn () => 'foobar', ], static function (BitlyApiParams $params): void { - self::assertEquals('token', $params->accessToken()); - self::assertFalse($params->importTags()); - self::assertFalse($params->importCustomDomains()); - self::assertFalse($params->keepCreationDate()); - self::assertFalse($params->ignoreArchived()); - self::assertEquals('foobar', $params->continueToken()); + self::assertEquals('token', $params->accessToken); + self::assertFalse($params->importTags); + self::assertFalse($params->importCustomDomains); + self::assertFalse($params->keepCreationDate); + self::assertFalse($params->ignoreArchived); + self::assertEquals('foobar', $params->continueToken); }]; yield [['access_token' => fn () => 'token'], static function (BitlyApiParams $params): void { - self::assertEquals('token', $params->accessToken()); - self::assertTrue($params->importTags()); - self::assertFalse($params->importCustomDomains()); - self::assertTrue($params->keepCreationDate()); - self::assertFalse($params->ignoreArchived()); - self::assertNull($params->continueToken()); + self::assertEquals('token', $params->accessToken); + self::assertTrue($params->importTags); + self::assertFalse($params->importCustomDomains); + self::assertTrue($params->keepCreationDate); + self::assertFalse($params->ignoreArchived); + self::assertNull($params->continueToken); }]; yield [[ 'access_token' => fn () => 'token', @@ -67,11 +67,11 @@ public function provideRawParams(): iterable 'keep_creation_date' => fn () => 'not bool', 'ignore_archived' => fn () => 'not bool', ], static function (BitlyApiParams $params): void { - self::assertEquals('token', $params->accessToken()); - self::assertTrue($params->importTags()); - self::assertTrue($params->importCustomDomains()); - self::assertTrue($params->keepCreationDate()); - self::assertTrue($params->ignoreArchived()); + self::assertEquals('token', $params->accessToken); + self::assertTrue($params->importTags); + self::assertTrue($params->importCustomDomains); + self::assertTrue($params->keepCreationDate); + self::assertTrue($params->ignoreArchived); }]; yield [[ 'access_token' => fn () => 'token', @@ -80,11 +80,11 @@ public function provideRawParams(): iterable 'keep_creation_date' => fn () => 0, 'ignore_archived' => fn () => 0, ], static function (BitlyApiParams $params): void { - self::assertEquals('token', $params->accessToken()); - self::assertFalse($params->importTags()); - self::assertFalse($params->importCustomDomains()); - self::assertFalse($params->keepCreationDate()); - self::assertFalse($params->ignoreArchived()); + self::assertEquals('token', $params->accessToken); + self::assertFalse($params->importTags); + self::assertFalse($params->importCustomDomains); + self::assertFalse($params->keepCreationDate); + self::assertFalse($params->ignoreArchived); }]; } } diff --git a/test/Sources/Bitly/BitlyApiProgressTrackerTest.php b/test/Sources/Bitly/BitlyApiProgressTrackerTest.php index 2373a75..637da1d 100644 --- a/test/Sources/Bitly/BitlyApiProgressTrackerTest.php +++ b/test/Sources/Bitly/BitlyApiProgressTrackerTest.php @@ -6,9 +6,9 @@ use DateInterval; use PHPUnit\Framework\TestCase; -use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiParams; use Shlinkio\Shlink\Importer\Sources\Bitly\BitlyApiProgressTracker; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Util\DateHelper; use function base64_encode; @@ -19,7 +19,7 @@ class BitlyApiProgressTrackerTest extends TestCase public function expectedContinueTokenIsGenerated(): void { $tracker = BitlyApiProgressTracker::initFromParams(BitlyApiParams::fromImportParams( - ImportParams::fromSourceAndCallableMap('', ['access_token' => fn () => '']), + ImportSource::BITLY->toParamsWithCallableMap(['access_token' => fn () => '']), )); self::assertNull($tracker->generateContinueToken()); @@ -38,7 +38,7 @@ public function expectedContinueTokenIsGenerated(): void public function initializesWithProvidedToken(): void { $tracker = BitlyApiProgressTracker::initFromParams(BitlyApiParams::fromImportParams( - ImportParams::fromSourceAndCallableMap('', [ + ImportSource::BITLY->toParamsWithCallableMap([ 'access_token' => fn () => '', 'continue_token' => fn () => base64_encode('abc123__1603378130'), ]), diff --git a/test/Sources/Csv/CsvImporterTest.php b/test/Sources/Csv/CsvImporterTest.php index 124aee9..4ab44ea 100644 --- a/test/Sources/Csv/CsvImporterTest.php +++ b/test/Sources/Csv/CsvImporterTest.php @@ -8,9 +8,8 @@ use DateTimeInterface; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; -use Shlinkio\Shlink\Importer\Params\ImportParams; use Shlinkio\Shlink\Importer\Sources\Csv\CsvImporter; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use function fopen; use function fwrite; @@ -31,8 +30,7 @@ protected function setUp(): void */ public function csvIsProperlyImported(string $csv, string $delimiter, array $expectedList): void { - $options = ImportParams::fromSourceAndCallableMap( - '', + $options = ImportSource::CSV->toParamsWithCallableMap( ['delimiter' => fn () => $delimiter, 'stream' => fn () => $this->createCsvStream($csv)], ); @@ -57,7 +55,7 @@ public function provideCSVs(): iterable ',', [ new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, 'https://shlink.io', ['foo', 'bar', 'baz'], $this->getDate(), @@ -66,7 +64,7 @@ public function provideCSVs(): iterable null, ), new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, 'https://facebook.com', [], $this->getDate(), @@ -86,7 +84,7 @@ public function provideCSVs(): iterable ';', [ new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, 'https://alejandrocelaya.blog', [], $this->getDate(), @@ -95,7 +93,7 @@ public function provideCSVs(): iterable null, ), new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, 'https://facebook.com', ['foo', 'baz'], $this->getDate(), @@ -104,7 +102,7 @@ public function provideCSVs(): iterable null, ), new ImportedShlinkUrl( - ImportSources::CSV, + ImportSource::CSV, 'https://shlink.io/documentation', [], $this->getDate(), diff --git a/test/Sources/Kutt/KuttImporterTest.php b/test/Sources/Kutt/KuttImporterTest.php index 99b0c8e..eca2d4d 100644 --- a/test/Sources/Kutt/KuttImporterTest.php +++ b/test/Sources/Kutt/KuttImporterTest.php @@ -15,8 +15,7 @@ use Shlinkio\Shlink\Importer\Http\RestApiConsumerInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrlMeta; -use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Sources\Kutt\KuttImporter; use function sprintf; @@ -43,7 +42,7 @@ public function exceptionsThrownByApiConsumerAreWrapped(): void $this->expectException(ImportException::class); $callApi->shouldBeCalledOnce(); - $result = $this->importer->import(ImportParams::fromSource('')); + $result = $this->importer->import(ImportSource::BITLY->toParams()); // The result is a generator, so we need to iterate it in order to trigger its logic foreach ($result as $element) { @@ -93,33 +92,33 @@ function (array $args) use (&$urlsCallNum, $loadAll): array { ); /** @var ImportedShlinkUrl[] $result */ - $result = $this->importer->import(ImportParams::fromSourceAndCallableMap('', [ + $result = $this->importer->import(ImportSource::BITLY->toParamsWithCallableMap([ 'api_key' => static fn () => 'my_api_key', 'import_all_urls' => static fn () => $loadAll, ])); foreach ($result as $index => $url) { - self::assertEquals(ImportSources::KUTT, $url->source()); + self::assertEquals(ImportSource::KUTT, $url->source); if ($index % 2 === 0) { - self::assertEquals(3, $url->visitsCount()); - self::assertEquals('https://longurl.com', $url->longUrl()); - self::assertEquals('doma.in', $url->domain()); - self::assertEquals(new DateTimeImmutable('2022-04-14T08:28:57.155Z'), $url->createdAt()); - self::assertEquals('short-code', $url->shortCode()); - self::assertNull($url->title()); + self::assertEquals(3, $url->visitsCount); + self::assertEquals('https://longurl.com', $url->longUrl); + self::assertEquals('doma.in', $url->domain); + self::assertEquals(new DateTimeImmutable('2022-04-14T08:28:57.155Z'), $url->createdAt); + self::assertEquals('short-code', $url->shortCode); + self::assertNull($url->title); self::assertEquals( new ImportedShlinkUrlMeta(null, new DateTimeImmutable('2023-04-16T00:00:00.000Z'), null), - $url->meta(), + $url->meta, ); } else { - self::assertEquals(25, $url->visitsCount()); - self::assertEquals('https://longurl-2.com', $url->longUrl()); - self::assertNull($url->domain()); - self::assertEquals(new DateTimeImmutable('2022-04-16T00:00:00.000Z'), $url->createdAt()); - self::assertEquals('short-code-2', $url->shortCode()); - self::assertEquals('foo link', $url->title()); - self::assertEquals(new ImportedShlinkUrlMeta(null, null, null), $url->meta()); + self::assertEquals(25, $url->visitsCount); + self::assertEquals('https://longurl-2.com', $url->longUrl); + self::assertNull($url->domain); + self::assertEquals(new DateTimeImmutable('2022-04-16T00:00:00.000Z'), $url->createdAt); + self::assertEquals('short-code-2', $url->shortCode); + self::assertEquals('foo link', $url->title); + self::assertEquals(new ImportedShlinkUrlMeta(null, null, null), $url->meta); } } diff --git a/test/Sources/Shlink/ShlinkImporterTest.php b/test/Sources/Shlink/ShlinkImporterTest.php index 91ada06..54e5b66 100644 --- a/test/Sources/Shlink/ShlinkImporterTest.php +++ b/test/Sources/Shlink/ShlinkImporterTest.php @@ -15,7 +15,7 @@ use Shlinkio\Shlink\Importer\Http\RestApiConsumerInterface; use Shlinkio\Shlink\Importer\Model\ImportedShlinkUrl; use Shlinkio\Shlink\Importer\Params\ImportParams; -use Shlinkio\Shlink\Importer\Sources\ImportSources; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Sources\Shlink\ShlinkImporter; use function array_merge; @@ -44,7 +44,7 @@ public function exceptionsThrownByApiConsumerAreWrapped(): void $this->expectException(ImportException::class); $callApi->shouldBeCalledOnce(); - $result = $this->importer->import(ImportParams::fromSource('')); + $result = $this->importer->import(ImportSource::BITLY->toParams()); // The result is a generator, so we need to iterate it in order to trigger its logic foreach ($result as $element) { @@ -128,7 +128,7 @@ function (array $args) use ($visit1, $visit2): array { ); /** @var ImportedShlinkUrl[] $result */ - $result = $this->importer->import(ImportParams::fromSourceAndCallableMap('', [ + $result = $this->importer->import(ImportSource::SHLINK->toParamsWithCallableMap([ 'api_key' => fn () => $apiKey, ImportParams::IMPORT_VISITS_PARAM => fn () => $doLoadVisits, ])); @@ -138,39 +138,39 @@ function (array $args) use ($visit1, $visit2): array { foreach ($result as $url) { $urls[] = $url; - self::assertEquals(ImportSources::SHLINK, $url->source()); - self::assertEquals('https://www.alejandrocelaya.com/foo', $url->longUrl()); - self::assertEquals(['bar', 'foo', 'website'], $url->tags()); + self::assertEquals(ImportSource::SHLINK, $url->source); + self::assertEquals('https://www.alejandrocelaya.com/foo', $url->longUrl); + self::assertEquals(['bar', 'foo', 'website'], $url->tags); self::assertEquals( DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2016-05-02T17:49:53+02:00'), - $url->createdAt(), + $url->createdAt, ); - self::assertNull($url->domain()); - self::assertEquals('rY9zd', $url->shortCode()); - self::assertEquals('', $url->title()); - self::assertEquals(48, $url->visitsCount()); - self::assertNull($url->meta()->validSince()); + self::assertNull($url->domain); + self::assertEquals('rY9zd', $url->shortCode); + self::assertEquals('', $url->title); + self::assertEquals(48, $url->visitsCount); + self::assertNull($url->meta->validSince); self::assertEquals( DateTimeImmutable::createFromFormat(DateTimeImmutable::ATOM, '2020-05-02T17:49:53+02:00'), - $url->meta()->validUntil(), + $url->meta->validUntil, ); - self::assertNull($url->meta()->maxVisits()); + self::assertNull($url->meta->maxVisits); - foreach ($url->visits() as $index => $visit) { + foreach ($url->visits as $index => $visit) { $visits[] = $visit; - self::assertEquals(contains([3, 4], $index) ? 'visit1' : 'visit2', $visit->referer()); + self::assertEquals(contains([3, 4], $index) ? 'visit1' : 'visit2', $visit->referer); self::assertEquals( 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.10', - $visit->userAgent(), + $visit->userAgent, ); - self::assertEquals('countryCode', $visit->location()->countryCode()); - self::assertEquals('countryName', $visit->location()->countryName()); - self::assertEquals('regionName', $visit->location()->regionName()); - self::assertEquals('cityName', $visit->location()->cityName()); - self::assertEquals('timezone', $visit->location()->timezone()); - self::assertEquals(0.0, $visit->location()->latitude()); - self::assertEquals(0.0, $visit->location()->longitude()); + self::assertEquals('countryCode', $visit->location->countryCode); + self::assertEquals('countryName', $visit->location->countryName); + self::assertEquals('regionName', $visit->location->regionName); + self::assertEquals('cityName', $visit->location->cityName); + self::assertEquals('timezone', $visit->location->timezone); + self::assertEquals(0.0, $visit->location->latitude); + self::assertEquals(0.0, $visit->location->longitude); } } @@ -228,13 +228,13 @@ function (array $args) use (&$urlsCallNum, $shortUrl): array { Argument::cetera(), )->willReturn([]); - $result = $this->importer->import(ImportParams::fromSourceAndCallableMap('', [ + $result = $this->importer->import(ImportSource::SHLINK->toParamsWithCallableMap([ 'api_key' => fn () => 'foo', ImportParams::IMPORT_VISITS_PARAM => fn () => true, ])); foreach ($result as $url) { // The result needs to be iterated in order to perform the calls - foreach ($url->visits() as $visit) { + foreach ($url->visits as $visit) { } } diff --git a/test/Sources/Yourls/YourlsImporterTest.php b/test/Sources/Yourls/YourlsImporterTest.php index 3bad20a..097b2d5 100644 --- a/test/Sources/Yourls/YourlsImporterTest.php +++ b/test/Sources/Yourls/YourlsImporterTest.php @@ -13,6 +13,7 @@ use Shlinkio\Shlink\Importer\Http\InvalidRequestException; use Shlinkio\Shlink\Importer\Http\RestApiConsumerInterface; use Shlinkio\Shlink\Importer\Params\ImportParams; +use Shlinkio\Shlink\Importer\Sources\ImportSource; use Shlinkio\Shlink\Importer\Sources\Yourls\YourlsImporter; use Shlinkio\Shlink\Importer\Sources\Yourls\YourlsMissingPluginException; use Throwable; @@ -47,7 +48,7 @@ public function exceptionsThrownByApiConsumerAreWrapped( $this->expectExceptionMessage($expectedMessage); $callApi->shouldBeCalledOnce(); - $result = $this->importer->import(ImportParams::fromSource('')); + $result = $this->importer->import(ImportSource::YOURLS->toParams()); // The result is a generator, so we need to iterate it in order to trigger its logic foreach ($result as $element) { @@ -131,29 +132,29 @@ function (array $args) { }, ); - $result = $this->importer->import(ImportParams::fromSourceAndCallableMap('', [ + $result = $this->importer->import(ImportSource::YOURLS->toParamsWithCallableMap([ 'username' => fn () => 'the_username', 'password' => fn () => 'the_password', ImportParams::IMPORT_VISITS_PARAM => fn () => $doLoadVisits, ])); foreach ($result as $urlIndex => $url) { - self::assertEquals('keyword_' . $urlIndex, $url->shortCode()); - self::assertEquals('url_' . $urlIndex, $url->longUrl()); - self::assertEquals('title_' . $urlIndex, $url->title()); - - foreach ($url->visits() as $visitIndex => $visit) { - self::assertEquals('user_agent_' . $visitIndex, $visit->userAgent()); - self::assertEquals('country_code_' . $visitIndex, $visit->location()->countryCode()); - self::assertEmpty($visit->location()->cityName()); - self::assertEmpty($visit->location()->countryName()); - self::assertEmpty($visit->location()->regionName()); - self::assertEmpty($visit->location()->timezone()); + self::assertEquals('keyword_' . $urlIndex, $url->shortCode); + self::assertEquals('url_' . $urlIndex, $url->longUrl); + self::assertEquals('title_' . $urlIndex, $url->title); + + foreach ($url->visits as $visitIndex => $visit) { + self::assertEquals('user_agent_' . $visitIndex, $visit->userAgent); + self::assertEquals('country_code_' . $visitIndex, $visit->location->countryCode); + self::assertEmpty($visit->location->cityName); + self::assertEmpty($visit->location->countryName); + self::assertEmpty($visit->location->regionName); + self::assertEmpty($visit->location->timezone); if ($visitIndex === 0) { - self::assertEquals('referrer_' . $visitIndex, $visit->referer()); + self::assertEquals('referrer_' . $visitIndex, $visit->referer); } else { - self::assertEmpty($visit->referer()); + self::assertEmpty($visit->referer); } } }