Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
716 changes: 139 additions & 577 deletions mago-analyze-baseline.toml

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions mago.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,12 @@ loop-does-not-iterate = { exclude = ["test"] }

[analyzer]
baseline = "mago-analyze-baseline.toml"
ignore = [
{ code = "mixed-assignment", in = ["test/", "test-integration/"] },
{ code = "mixed-argument", in = ["test/", "test-integration/"] },
{ code = "mixed-operand", in = ["test/", "test-integration/"] },
{ code = "invalid-method-access", in = ["test/", "test-integration/"] },
{ code = "string-member-selector", in = ["test/", "test-integration/"] },
{ code = "invalid-array-element-key", in = ["test/", "test-integration/"] },
{ code = "possibly-false-argument", in = ["test/", "test-integration/"] },
]
58 changes: 36 additions & 22 deletions src/Config/ArrayShlinkConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,54 @@

final readonly class ArrayShlinkConfig implements ShlinkConfigInterface
{
public const BASE_URL_PROP = 'baseUrl';
public const API_KEY_PROP = 'apiKey';
public const VERSION_PROP = 'version';
public const string BASE_URL_PROP = 'baseUrl';
public const string API_KEY_PROP = 'apiKey';
public const string VERSION_PROP = 'version';

private function __construct(private ShlinkConfigInterface $wrapped) {}

/**
* @param array{
* ArrayShlinkConfig::BASE_URL_PROP?: string,
* ArrayShlinkConfig::API_KEY_PROP?: string,
* ArrayShlinkConfig::VERSION_PROP?: string,
* } $config
* @throws InvalidConfigException
*/
public static function fromArray(array $config): self
{
return new self(ShlinkConfig::fromRawConfig(new class($config) implements RawConfigInterface {
public function __construct(private readonly array $config) {}
return new self(ShlinkConfig::fromRawConfig(
new readonly class($config) implements RawConfigInterface {
/**
* @param array{
* ArrayShlinkConfig::BASE_URL_PROP?: string,
* ArrayShlinkConfig::API_KEY_PROP?: string,
* ArrayShlinkConfig::VERSION_PROP?: string,
* } $config
*/
public function __construct(private array $config) {}

public function baseUrl(): string
{
return trim($this->config[ArrayShlinkConfig::BASE_URL_PROP] ?? '');
}
public function baseUrl(): string
{
return trim($this->config[ArrayShlinkConfig::BASE_URL_PROP] ?? '');
}

public function apiKey(): string
{
return trim($this->config[ArrayShlinkConfig::API_KEY_PROP] ?? '');
}
public function apiKey(): string
{
return trim($this->config[ArrayShlinkConfig::API_KEY_PROP] ?? '');
}

public function version(): string
{
return trim($this->config[ArrayShlinkConfig::VERSION_PROP] ?? '');
}
public function version(): string
{
return trim($this->config[ArrayShlinkConfig::VERSION_PROP] ?? '');
}

public function missingConfigException(): InvalidConfigException
{
return InvalidConfigException::forInvalidConfig();
}
}));
public function missingConfigException(): InvalidConfigException
{
return InvalidConfigException::forInvalidConfig();
}
},
));
}

public function baseUrl(): string
Expand Down
3 changes: 3 additions & 0 deletions src/Domains/Exception/DomainNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ private function __construct(HttpException $previous, public readonly string $au
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{authority?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$authority = $prev->additional['authority'] ?? '';
Expand Down
7 changes: 7 additions & 0 deletions src/Domains/Model/Domain.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
{
private function __construct(public string $domain, public bool $isDefault, public DomainRedirects $redirects) {}

/**
* @param array{
* domain?: string,
* isDefault?: bool,
* redirects?: array{baseUrlRedirect?: string, regular404Redirect?: string, invalidShortUrlRedirect?: string}
* } $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
3 changes: 3 additions & 0 deletions src/Domains/Model/DomainRedirects.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ private function __construct(
public string|null $invalidShortUrlRedirect,
) {}

/**
* @param array{baseUrlRedirect?: string, regular404Redirect?: string, invalidShortUrlRedirect?: string} $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
3 changes: 3 additions & 0 deletions src/Exception/InvalidDataException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ private function __construct(HttpException $previous, public readonly array $inv
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{invalidElements?: string[]}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$invalidElements = $prev->additional['invalidElements'] ?? [];
Expand Down
14 changes: 12 additions & 2 deletions src/Http/Exception/HttpException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@

use const ARRAY_FILTER_USE_KEY;

/**
* @template AdditionalT of array<string, mixed>
*/
class HttpException extends RuntimeException implements ExceptionInterface
{
private const STANDARD_PROBLEM_DETAILS_PROPS = ['type', 'title', 'detail', 'status'];
private const array STANDARD_PROBLEM_DETAILS_PROPS = ['type', 'title', 'detail', 'status'];

/**
* @param AdditionalT $additional
*/
private function __construct(
public readonly ErrorType $type,
public readonly string $title,
Expand All @@ -35,11 +41,15 @@ public static function fromNonSuccessfulResponse(ResponseInterface $resp): self
return self::fromPayload(JsonDecoder::decode($resp->getBody()->__toString()));
}

/**
* @param array{type?: string, title?: string, detail?: string, status?: int, ...<string, mixed>} $payload
*/
public static function fromPayload(array $payload): self
{
/** @var AdditionalT $additional */
$additional = array_filter(
$payload,
static fn (string $key) => !in_array($key, self::STANDARD_PROBLEM_DETAILS_PROPS, true),
static fn (string $key) => !in_array($key, self::STANDARD_PROBLEM_DETAILS_PROPS, strict: true),
ARRAY_FILTER_USE_KEY,
);

Expand Down
2 changes: 1 addition & 1 deletion src/Model/ListEndpointIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ abstract class ListEndpointIterator implements IteratorAggregate, Countable
private array $currentPagination = [];

/**
* @param Closure(int $page, int $itemsPerPage): array{array, array} $pageLoader
* @param Closure(int $page, int $itemsPerPage): list{array, array} $pageLoader
* @param Closure(mixed): mixed $itemMapper
*/
public function __construct(
Expand Down
3 changes: 3 additions & 0 deletions src/RedirectRules/Model/RedirectCondition.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public static function forBrowser(Browser $browser): self
return new self(RedirectConditionType::BROWSER, $browser->value);
}

/**
* @param array{type?: string, matchValue?: string, matchKey?: string} $payload
*/
public static function fromArray(array $payload): self
{
$originalType = $payload['type'] ?? '';
Expand Down
7 changes: 7 additions & 0 deletions src/RedirectRules/Model/RedirectRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
*/
private function __construct(public string $longUrl, public int $priority, public array $conditions) {}

/**
* @param array{
* longUrl?: string,
* priority?: positive-int,
* conditions?: list<array{type?: string, matchValue?: string, matchKey?: string}>
* } $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
3 changes: 3 additions & 0 deletions src/RedirectRules/Model/RedirectRulesList.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/
private function __construct(public string $defaultLongUrl, public array $redirectRules) {}

/**
* @param array{defaultLongUrl?: string, redirectRules?: list<mixed>} $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
3 changes: 3 additions & 0 deletions src/ShortUrls/Exception/DeleteShortUrlThresholdException.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ private function __construct(
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{shortCode?: string, domain?: string, threshold?: int}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$shortCode = $prev->additional['shortCode'] ?? '';
Expand Down
3 changes: 3 additions & 0 deletions src/ShortUrls/Exception/InvalidLongUrlException.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ private function __construct(HttpException $previous, public readonly string $lo
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{url?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$longUrl = $prev->additional['url'] ?? '';
Expand Down
3 changes: 3 additions & 0 deletions src/ShortUrls/Exception/NonUniqueSlugException.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ private function __construct(
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{customSlug?: string, domain?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$customSlug = $prev->additional['customSlug'] ?? '';
Expand Down
3 changes: 3 additions & 0 deletions src/ShortUrls/Exception/ShortUrlNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ private function __construct(HttpException $previous, public readonly ShortUrlId
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{shortCode?: string, domain?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$shortCode = $prev->additional['shortCode'] ?? '';
Expand Down
18 changes: 17 additions & 1 deletion src/ShortUrls/Model/ShortUrl.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,29 @@ private function __construct(
public VisitsSummary $visitsSummary,
) {}

/**
* @param array{
* shortCode?: string,
* shortUrl?: string,
* longUrl?: string,
* dateCreated?: string,
* domain?: string,
* title?: string,
* crawlable?: bool,
* forwardQuery?: bool,
* hasRedirectRules?: bool,
* tags?: string[],
* meta?: array{validSince?: string, validUntil?: string, maxVisits?: positive-int},
* visitsSummary?: array{total?: int<0, max>, nonBots?: int<0, max>, bots?: int<0, max>},
* } $payload
*/
public static function fromArray(array $payload): self
{
return new self(
shortCode: $payload['shortCode'] ?? '',
shortUrl: $payload['shortUrl'] ?? '',
longUrl: $payload['longUrl'] ?? '',
// @phpstan-ignore-next-line
// @mago-ignore
dateCreated: DateTimeImmutable::createFromFormat(DateTimeInterface::ATOM, $payload['dateCreated']),
domain: $payload['domain'] ?? null,
title: $payload['title'] ?? null,
Expand Down
3 changes: 3 additions & 0 deletions src/ShortUrls/Model/ShortUrlMeta.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ private function __construct(
public int|null $maxVisits,
) {}

/**
* @param array{validSince?: string, validUntil?: string, maxVisits?: positive-int} $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
2 changes: 1 addition & 1 deletion src/ShortUrls/Model/ShortUrlsList.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ private function __construct(Closure $pageLoader)
}

/**
* @param Closure(int $page, int $itemsPerPage): array{array, array} $pageLoader
* @param Closure(int $page, int $itemsPerPage): list{list, list} $pageLoader
*/
public static function forTupleLoader(Closure $pageLoader): self
{
Expand Down
3 changes: 3 additions & 0 deletions src/Tags/Exception/TagConflictException.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ private function __construct(
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{oldName?: string, newName?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$oldName = $prev->additional['oldName'] ?? '';
Expand Down
3 changes: 3 additions & 0 deletions src/Tags/Exception/TagNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ private function __construct(HttpException $previous, public readonly string $ta
parent::__construct($previous->detail, $previous->status, $previous);
}

/**
* @param HttpException<array{tag?: string}> $prev
*/
public static function fromHttpException(HttpException $prev): self
{
$tag = $prev->additional['tag'] ?? '';
Expand Down
10 changes: 10 additions & 0 deletions src/Tags/Model/TagWithStats.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,18 @@

final readonly class TagWithStats
{
/**
* @param int<0, max> $shortUrlsCount
*/
private function __construct(public string $tag, public int $shortUrlsCount, public VisitsSummary $visitsSummary) {}

/**
* @param array{
* tag?: string,
* shortUrlsCount?: int<0, max>,
* visitsSummary?: array{total?: int<0, max>, nonBots?: int<0, max>, bots?: int<0, max>}
* } $payload
*/
public static function fromArray(array $payload): self
{
return new self(
Expand Down
4 changes: 2 additions & 2 deletions src/Tags/Model/TagsWithStatsList.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ private function __construct(Closure $pageLoader, int $itemsPerPage)
}

/**
* @param Closure(int $page, int $itemsPerPage): array{array, array} $pageLoader
* @param Closure(int $page, int $itemsPerPage): list{array, array} $pageLoader
*/
public static function forTupleLoader(Closure $pageLoader): self
{
return new self($pageLoader, self::PAGINATED_ITEMS_PER_PAGE);
}

/**
* @param Closure(int $page, int $itemsPerPage): array{array, array} $pageLoader
* @param Closure(int $page, int $itemsPerPage): list{array, array} $pageLoader
*/
public static function forNonPaginatedTupleLoader(Closure $pageLoader): self
{
Expand Down
7 changes: 6 additions & 1 deletion src/Tags/TagsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ public function listTagsWithStatsWithFilter(TagsFilter $filter): TagsWithStatsLi
};
$tupleLoader = function (int $page, int $itemsPerPage) use ($buildQueryWithPage): array {
$payload = $this->httpClient->getFromShlink('/tags/stats', $buildQueryWithPage($page, $itemsPerPage));
return [$payload['tags']['data'] ?? [], $payload['tags']['pagination'] ?? []];
/** @var array $data */
$data = $payload['tags']['data'] ?? [];
/** @var array $pagination */
$pagination = $payload['tags']['pagination'] ?? [];

return [$data, $pagination];
};

return $filter->shouldPaginateRequest()
Expand Down
3 changes: 2 additions & 1 deletion src/Utils/JsonDecoder.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class JsonDecoder
*/
public static function decode(string $data): array
{
return json_decode($data, true, 512, JSON_THROW_ON_ERROR);
// @mago-expect analysis:mixed-return-statement
return json_decode($data, associative: true, flags: JSON_THROW_ON_ERROR);
}
}
Loading
Loading