Merge pull request #1097 from acelaya-forks/feature/php8

Feature/php8
This commit is contained in:
Alejandro Celaya 2021-05-23 12:54:12 +02:00 committed by GitHub
commit a68f450d36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 359 additions and 838 deletions

View file

@ -48,7 +48,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -63,7 +63,7 @@ jobs:
- run: composer install --no-interaction --prefer-dist
- run: composer test:unit:ci
- uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '7.4' }}
if: ${{ matrix.php-version == '8.0' }}
with:
name: coverage-unit
path: |
@ -74,7 +74,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -89,7 +89,7 @@ jobs:
- run: composer install --no-interaction --prefer-dist
- run: composer test:db:sqlite:ci
- uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '7.4' }}
if: ${{ matrix.php-version == '8.0' }}
with:
name: coverage-db
path: |
@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -120,7 +120,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -140,7 +140,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -160,7 +160,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -184,7 +184,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
steps:
- name: Checkout code
uses: actions/checkout@v2
@ -201,7 +201,7 @@ jobs:
- run: composer install --no-interaction --prefer-dist
- run: bin/test/run-api-tests.sh
- uses: actions/upload-artifact@v2
if: ${{ matrix.php-version == '7.4' }}
if: ${{ matrix.php-version == '8.0' }}
with:
name: coverage-api
path: |
@ -216,7 +216,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
test-group: ['unit', 'db']
steps:
- name: Checkout code

View file

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: ['7.4', '8.0']
php-version: ['8.0']
swoole: ['yes', 'no']
steps:
- name: Checkout code
@ -54,7 +54,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-version: [ '7.4', '8.0' ]
php-version: [ '8.0' ]
swoole: [ 'yes', 'no' ]
steps:
- uses: geekyeggo/delete-artifact@v1

View file

@ -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).
## [Unreleased]
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* [#1046](https://github.com/shlinkio/shlink/issues/1046) Dropped support for PHP 7.4.
### Fixed
* *Nothing*
## [2.7.0] - 2021-05-23
### Added
* [#1044](https://github.com/shlinkio/shlink/issues/1044) Added ability to set names on API keys, which helps to identify them when the list grows.

View file

@ -33,7 +33,7 @@ The idea is that you can just generate a container using the image and provide t
First, make sure the host where you are going to run shlink fulfills these requirements:
* PHP 7.4 or 8.0
* PHP 8.0
* The next PHP extensions: json, curl, pdo, intl, gd and gmp.
* apcu extension is recommended if you don't plan to use swoole.
* xml extension is required if you want to generate QR codes in svg format.

View file

@ -12,7 +12,7 @@
}
],
"require": {
"php": "^7.4 || ^8.0",
"php": "^8.0",
"ext-json": "*",
"ext-pdo": "*",
"akrabat/ip-address-middleware": "^2.0",

View file

@ -11,7 +11,7 @@ server {
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
}

View file

@ -57,7 +57,7 @@ final class Version20180913205455 extends AbstractMigration
try {
return (string) IpAddress::fromString($addr)->getAnonymizedCopy();
} catch (InvalidArgumentException $e) {
} catch (InvalidArgumentException) {
return null;
}
}

View file

@ -10,11 +10,8 @@ use Symfony\Component\Console\Input\InputInterface;
class RoleResolver implements RoleResolverInterface
{
private DomainServiceInterface $domainService;
public function __construct(DomainServiceInterface $domainService)
public function __construct(private DomainServiceInterface $domainService)
{
$this->domainService = $domainService;
}
public function determineRoles(InputInterface $input): array

View file

@ -19,12 +19,9 @@ class DisableKeyCommand extends Command
{
public const NAME = 'api-key:disable';
private ApiKeyServiceInterface $apiKeyService;
public function __construct(ApiKeyServiceInterface $apiKeyService)
public function __construct(private ApiKeyServiceInterface $apiKeyService)
{
parent::__construct();
$this->apiKeyService = $apiKeyService;
}
protected function configure(): void

View file

@ -23,14 +23,11 @@ class GenerateKeyCommand extends BaseCommand
{
public const NAME = 'api-key:generate';
private ApiKeyServiceInterface $apiKeyService;
private RoleResolverInterface $roleResolver;
public function __construct(ApiKeyServiceInterface $apiKeyService, RoleResolverInterface $roleResolver)
{
public function __construct(
private ApiKeyServiceInterface $apiKeyService,
private RoleResolverInterface $roleResolver
) {
parent::__construct();
$this->apiKeyService = $apiKeyService;
$this->roleResolver = $roleResolver;
}
protected function configure(): void

View file

@ -27,12 +27,9 @@ class ListKeysCommand extends BaseCommand
public const NAME = 'api-key:list';
private ApiKeyServiceInterface $apiKeyService;
public function __construct(ApiKeyServiceInterface $apiKeyService)
public function __construct(private ApiKeyServiceInterface $apiKeyService)
{
parent::__construct();
$this->apiKeyService = $apiKeyService;
}
protected function configure(): void
@ -61,7 +58,7 @@ class ListKeysCommand extends BaseCommand
if (! $enabledOnly) {
$rowData[] = sprintf($messagePattern, $this->getEnabledSymbol($apiKey));
}
$rowData[] = $expiration !== null ? $expiration->toAtomString() : '-';
$rowData[] = $expiration?->toAtomString() ?? '-';
$rowData[] = $apiKey->isAdmin() ? 'Admin' : implode("\n", $apiKey->mapRoles(
fn (string $roleName, array $meta) =>
empty($meta)

View file

@ -22,7 +22,7 @@ abstract class BaseCommand extends Command
?string $shortcut = null,
?int $mode = null,
string $description = '',
$default = null
$default = null,
): self {
$this->addOption($name, $shortcut, $mode, $description, $default);

View file

@ -13,16 +13,14 @@ use Symfony\Component\Process\PhpExecutableFinder;
abstract class AbstractDatabaseCommand extends AbstractLockedCommand
{
private ProcessRunnerInterface $processRunner;
private string $phpBinary;
public function __construct(
LockFactory $locker,
ProcessRunnerInterface $processRunner,
private ProcessRunnerInterface $processRunner,
PhpExecutableFinder $phpFinder
) {
parent::__construct($locker);
$this->processRunner = $processRunner;
$this->phpBinary = $phpFinder->find(false) ?: 'php';
}

View file

@ -21,19 +21,14 @@ class CreateDatabaseCommand extends AbstractDatabaseCommand
public const DOCTRINE_SCRIPT = 'vendor/doctrine/orm/bin/doctrine.php';
public const DOCTRINE_CREATE_SCHEMA_COMMAND = 'orm:schema-tool:create';
private Connection $regularConn;
private Connection $noDbNameConn;
public function __construct(
LockFactory $locker,
ProcessRunnerInterface $processRunner,
PhpExecutableFinder $phpFinder,
Connection $conn,
Connection $noDbNameConn
private Connection $regularConn,
private Connection $noDbNameConn
) {
parent::__construct($locker, $processRunner, $phpFinder);
$this->regularConn = $conn;
$this->noDbNameConn = $noDbNameConn;
}
protected function configure(): void

View file

@ -18,12 +18,9 @@ class ListDomainsCommand extends Command
{
public const NAME = 'domain:list';
private DomainServiceInterface $domainService;
public function __construct(DomainServiceInterface $domainService)
public function __construct(private DomainServiceInterface $domainService)
{
parent::__construct();
$this->domainService = $domainService;
}
protected function configure(): void

View file

@ -21,12 +21,9 @@ class DeleteShortUrlCommand extends Command
{
public const NAME = 'short-url:delete';
private DeleteShortUrlServiceInterface $deleteShortUrlService;
public function __construct(DeleteShortUrlServiceInterface $deleteShortUrlService)
public function __construct(private DeleteShortUrlServiceInterface $deleteShortUrlService)
{
parent::__construct();
$this->deleteShortUrlService = $deleteShortUrlService;
}
protected function configure(): void

View file

@ -30,19 +30,12 @@ class GenerateShortUrlCommand extends BaseCommand
{
public const NAME = 'short-url:generate';
private UrlShortenerInterface $urlShortener;
private ShortUrlStringifierInterface $stringifier;
private int $defaultShortCodeLength;
public function __construct(
UrlShortenerInterface $urlShortener,
ShortUrlStringifierInterface $stringifier,
int $defaultShortCodeLength
private UrlShortenerInterface $urlShortener,
private ShortUrlStringifierInterface $stringifier,
private int $defaultShortCodeLength
) {
parent::__construct();
$this->urlShortener = $urlShortener;
$this->stringifier = $stringifier;
$this->defaultShortCodeLength = $defaultShortCodeLength;
}
protected function configure(): void

View file

@ -27,11 +27,8 @@ class GetVisitsCommand extends AbstractWithDateRangeCommand
{
public const NAME = 'short-url:visits';
private VisitsStatsHelperInterface $visitsHelper;
public function __construct(VisitsStatsHelperInterface $visitsHelper)
public function __construct(private VisitsStatsHelperInterface $visitsHelper)
{
$this->visitsHelper = $visitsHelper;
parent::__construct();
}

View file

@ -33,14 +33,11 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
public const NAME = 'short-url:list';
private ShortUrlServiceInterface $shortUrlService;
private DataTransformerInterface $transformer;
public function __construct(ShortUrlServiceInterface $shortUrlService, DataTransformerInterface $transformer)
{
public function __construct(
private ShortUrlServiceInterface $shortUrlService,
private DataTransformerInterface $transformer
) {
parent::__construct();
$this->shortUrlService = $shortUrlService;
$this->transformer = $transformer;
}
protected function doConfigure(): void
@ -129,8 +126,8 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
ShortUrlsParamsInputFilter::TAGS => $tags,
ShortUrlsOrdering::ORDER_BY => $orderBy,
ShortUrlsParamsInputFilter::START_DATE => $startDate !== null ? $startDate->toAtomString() : null,
ShortUrlsParamsInputFilter::END_DATE => $endDate !== null ? $endDate->toAtomString() : null,
ShortUrlsParamsInputFilter::START_DATE => $startDate?->toAtomString(),
ShortUrlsParamsInputFilter::END_DATE => $endDate?->toAtomString(),
];
if ($all) {
@ -158,7 +155,7 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
OutputInterface $output,
array $columnsMap,
ShortUrlsParams $params,
bool $all
bool $all,
): Paginator {
$shortUrls = $this->shortUrlService->listShortUrls($params);
@ -203,14 +200,11 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
}
if ($input->getOption('show-api-key')) {
$columnsMap['API Key'] = static fn (array $_, ShortUrl $shortUrl): string =>
(string) $shortUrl->authorApiKey();
(string) $shortUrl->authorApiKey();
}
if ($input->getOption('show-api-key-name')) {
$columnsMap['API Key Name'] = static function (array $_, ShortUrl $shortUrl): ?string {
$apiKey = $shortUrl->authorApiKey();
return $apiKey !== null ? $apiKey->name() : null;
};
$columnsMap['API Key Name'] = static fn (array $_, ShortUrl $shortUrl): ?string =>
$shortUrl->authorApiKey()?->name();
}
return $columnsMap;

View file

@ -21,12 +21,9 @@ class ResolveUrlCommand extends Command
{
public const NAME = 'short-url:parse';
private ShortUrlResolverInterface $urlResolver;
public function __construct(ShortUrlResolverInterface $urlResolver)
public function __construct(private ShortUrlResolverInterface $urlResolver)
{
parent::__construct();
$this->urlResolver = $urlResolver;
}
protected function configure(): void

View file

@ -17,12 +17,9 @@ class CreateTagCommand extends Command
{
public const NAME = 'tag:create';
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService)
public function __construct(private TagServiceInterface $tagService)
{
parent::__construct();
$this->tagService = $tagService;
}
protected function configure(): void

View file

@ -16,12 +16,9 @@ class DeleteTagsCommand extends Command
{
public const NAME = 'tag:delete';
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService)
public function __construct(private TagServiceInterface $tagService)
{
parent::__construct();
$this->tagService = $tagService;
}
protected function configure(): void

View file

@ -18,12 +18,9 @@ class ListTagsCommand extends Command
{
public const NAME = 'tag:list';
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService)
public function __construct(private TagServiceInterface $tagService)
{
parent::__construct();
$this->tagService = $tagService;
}
protected function configure(): void

View file

@ -19,12 +19,9 @@ class RenameTagCommand extends Command
{
public const NAME = 'tag:rename';
private TagServiceInterface $tagService;
public function __construct(TagServiceInterface $tagService)
public function __construct(private TagServiceInterface $tagService)
{
parent::__construct();
$this->tagService = $tagService;
}
protected function configure(): void

View file

@ -14,12 +14,9 @@ use function sprintf;
abstract class AbstractLockedCommand extends Command
{
private LockFactory $locker;
public function __construct(LockFactory $locker)
public function __construct(private LockFactory $locker)
{
parent::__construct();
$this->locker = $locker;
}
final protected function execute(InputInterface $input, OutputInterface $output): ?int

View file

@ -63,7 +63,7 @@ abstract class AbstractWithDateRangeCommand extends BaseCommand
));
if ($output->isVeryVerbose()) {
$this->getApplication()->renderThrowable($e, $output);
$this->getApplication()?->renderThrowable($e, $output);
}
return null;

View file

@ -8,15 +8,11 @@ final class LockedCommandConfig
{
public const DEFAULT_TTL = 600.0; // 10 minutes
private string $lockName;
private bool $isBlocking;
private float $ttl;
private function __construct(string $lockName, bool $isBlocking, float $ttl = self::DEFAULT_TTL)
{
$this->lockName = $lockName;
$this->isBlocking = $isBlocking;
$this->ttl = $ttl;
private function __construct(
private string $lockName,
private bool $isBlocking,
private float $ttl = self::DEFAULT_TTL
) {
}
public static function blocking(string $lockName): self

View file

@ -19,13 +19,11 @@ class DownloadGeoLiteDbCommand extends Command
{
public const NAME = 'visit:download-db';
private GeolocationDbUpdaterInterface $dbUpdater;
private ?ProgressBar $progressBar = null;
public function __construct(GeolocationDbUpdaterInterface $dbUpdater)
public function __construct(private GeolocationDbUpdaterInterface $dbUpdater)
{
parent::__construct();
$this->dbUpdater = $dbUpdater;
}
protected function configure(): void
@ -71,7 +69,7 @@ class DownloadGeoLiteDbCommand extends Command
}
if ($io->isVerbose()) {
$this->getApplication()->renderThrowable($e, $io);
$this->getApplication()?->renderThrowable($e, $io);
}
return $olderDbExists ? ExitCodes::EXIT_WARNING : ExitCodes::EXIT_FAILURE;

View file

@ -30,19 +30,14 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
{
public const NAME = 'visit:locate';
private VisitLocatorInterface $visitLocator;
private IpLocationResolverInterface $ipLocationResolver;
private SymfonyStyle $io;
public function __construct(
VisitLocatorInterface $visitLocator,
IpLocationResolverInterface $ipLocationResolver,
private VisitLocatorInterface $visitLocator,
private IpLocationResolverInterface $ipLocationResolver,
LockFactory $locker
) {
parent::__construct($locker);
$this->visitLocator = $visitLocator;
$this->ipLocationResolver = $ipLocationResolver;
}
protected function configure(): void
@ -124,7 +119,7 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
} catch (Throwable $e) {
$this->io->error($e->getMessage());
if ($this->io->isVerbose()) {
$this->getApplication()->renderThrowable($e, $this->io);
$this->getApplication()?->renderThrowable($e, $this->io);
}
return ExitCodes::EXIT_FAILURE;
@ -156,7 +151,7 @@ class LocateVisitsCommand extends AbstractLockedCommand implements VisitGeolocat
} catch (WrongIpException $e) {
$this->io->writeln(' [<fg=red>An error occurred while locating IP. Skipped</>]');
if ($this->io->isVerbose()) {
$this->getApplication()->renderThrowable($e, $this->io);
$this->getApplication()?->renderThrowable($e, $this->io);
}
throw IpCannotBeLocatedException::forError($e);

View file

@ -18,15 +18,11 @@ class GeolocationDbUpdater implements GeolocationDbUpdaterInterface
{
private const LOCK_NAME = 'geolocation-db-update';
private DbUpdaterInterface $dbUpdater;
private Reader $geoLiteDbReader;
private LockFactory $locker;
public function __construct(DbUpdaterInterface $dbUpdater, Reader $geoLiteDbReader, LockFactory $locker)
{
$this->dbUpdater = $dbUpdater;
$this->geoLiteDbReader = $geoLiteDbReader;
$this->locker = $locker;
public function __construct(
private DbUpdaterInterface $dbUpdater,
private Reader $geoLiteDbReader,
private LockFactory $locker
) {
}
/**

View file

@ -18,12 +18,10 @@ use function str_replace;
class ProcessRunner implements ProcessRunnerInterface
{
private ProcessHelper $helper;
private Closure $createProcess;
public function __construct(ProcessHelper $helper, ?callable $createProcess = null)
public function __construct(private ProcessHelper $helper, ?callable $createProcess = null)
{
$this->helper = $helper;
$this->createProcess = $createProcess !== null
? Closure::fromCallable($createProcess)
: static fn (array $cmd) => new Process($cmd, null, null, null, LockedCommandConfig::DEFAULT_TTL);

View file

@ -12,11 +12,8 @@ final class ShlinkTable
private const DEFAULT_STYLE_NAME = 'default';
private const TABLE_TITLE_STYLE = '<options=bold> %s </>';
private ?Table $baseTable;
public function __construct(Table $baseTable)
public function __construct(private Table $baseTable)
{
$this->baseTable = $baseTable;
}
public static function fromOutput(OutputInterface $output): self

View file

@ -33,7 +33,7 @@ class RoleResolverTest extends TestCase
public function properRolesAreResolvedBasedOnInput(
InputInterface $input,
array $expectedRoles,
int $expectedDomainCalls
int $expectedDomainCalls,
): void {
$getDomain = $this->domainService->getOrCreate('example.com')->willReturn(
(new Domain('example.com'))->setId('1'),

View file

@ -74,7 +74,7 @@ class DeleteShortUrlCommandTest extends TestCase
public function deleteIsRetriedWhenThresholdIsReachedAndQuestionIsAccepted(
array $retryAnswer,
int $expectedDeleteCalls,
string $expectedMessage
string $expectedMessage,
): void {
$shortCode = 'abc123';
$identifier = new ShortUrlIdentifier($shortCode);

View file

@ -110,7 +110,7 @@ class ListShortUrlsCommandTest extends TestCase
array $input,
array $expectedContents,
array $notExpectedContents,
ApiKey $apiKey
ApiKey $apiKey,
): void {
$this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance())
->willReturn(new Paginator(new ArrayAdapter([
@ -185,7 +185,7 @@ class ListShortUrlsCommandTest extends TestCase
?string $searchTerm,
array $tags,
?string $startDate = null,
?string $endDate = null
?string $endDate = null,
): void {
$listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([
'page' => $page,

View file

@ -36,7 +36,7 @@ class DownloadGeoLiteDbCommandTest extends TestCase
public function showsProperMessageWhenGeoLiteUpdateFails(
bool $olderDbExists,
string $expectedMessage,
int $expectedExitCode
int $expectedExitCode,
): void {
$checkDbUpdate = $this->dbUpdater->checkDbUpdate(Argument::cetera())->will(
function (array $args) use ($olderDbExists): void {

View file

@ -73,7 +73,7 @@ class LocateVisitsCommandTest extends TestCase
int $expectedEmptyCalls,
int $expectedAllCalls,
bool $expectWarningPrint,
array $args
array $args,
): void {
$visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('', '', '1.2.3.4', ''));
$location = VisitLocation::fromGeolocation(Location::emptyInstance());

View file

@ -51,20 +51,12 @@ function parseDateRangeFromQuery(array $query, string $startDateName, string $en
$startDate = parseDateFromQuery($query, $startDateName);
$endDate = parseDateFromQuery($query, $endDateName);
// TODO Use match expression when migrating to PHP8
if ($startDate === null && $endDate === null) {
return DateRange::emptyInstance();
}
if ($startDate !== null && $endDate !== null) {
return DateRange::withStartAndEndDate($startDate, $endDate);
}
if ($startDate !== null) {
return DateRange::withStartDate($startDate);
}
return DateRange::withEndDate($endDate);
return match (true) {
$startDate === null && $endDate === null => DateRange::emptyInstance(),
$startDate !== null && $endDate !== null => DateRange::withStartAndEndDate($startDate, $endDate),
$startDate !== null => DateRange::withStartDate($startDate),
default => DateRange::withEndDate($endDate),
};
}
/**

View file

@ -27,20 +27,14 @@ use function array_merge;
abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMethodInterface
{
private ShortUrlResolverInterface $urlResolver;
private VisitsTrackerInterface $visitTracker;
private TrackingOptions $trackingOptions;
private LoggerInterface $logger;
public function __construct(
ShortUrlResolverInterface $urlResolver,
VisitsTrackerInterface $visitTracker,
TrackingOptions $trackingOptions,
private ShortUrlResolverInterface $urlResolver,
private VisitsTrackerInterface $visitTracker,
private TrackingOptions $trackingOptions,
?LoggerInterface $logger = null
) {
$this->urlResolver = $urlResolver;
$this->visitTracker = $visitTracker;
$this->trackingOptions = $trackingOptions;
$this->logger = $logger ?? new NullLogger();
}
@ -90,6 +84,6 @@ abstract class AbstractTrackingAction implements MiddlewareInterface, RequestMet
abstract protected function createErrorResp(
ServerRequestInterface $request,
RequestHandlerInterface $handler
RequestHandlerInterface $handler,
): ResponseInterface;
}

View file

@ -18,7 +18,7 @@ class PixelAction extends AbstractTrackingAction
protected function createErrorResp(
ServerRequestInterface $request,
RequestHandlerInterface $handler
RequestHandlerInterface $handler,
): ResponseInterface {
return new PixelResponse();
}

View file

@ -24,18 +24,14 @@ class QrCodeAction implements MiddlewareInterface
private const MIN_SIZE = 50;
private const MAX_SIZE = 1000;
private ShortUrlResolverInterface $urlResolver;
private ShortUrlStringifierInterface $stringifier;
private LoggerInterface $logger;
public function __construct(
ShortUrlResolverInterface $urlResolver,
ShortUrlStringifierInterface $stringifier,
private ShortUrlResolverInterface $urlResolver,
private ShortUrlStringifierInterface $stringifier,
?LoggerInterface $logger = null
) {
$this->urlResolver = $urlResolver;
$this->logger = $logger ?? new NullLogger();
$this->stringifier = $stringifier;
}
public function process(Request $request, RequestHandlerInterface $handler): Response

View file

@ -16,17 +16,14 @@ use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
class RedirectAction extends AbstractTrackingAction implements StatusCodeInterface
{
private RedirectResponseHelperInterface $redirectResponseHelper;
public function __construct(
ShortUrlResolverInterface $urlResolver,
VisitsTrackerInterface $visitTracker,
Options\TrackingOptions $trackingOptions,
RedirectResponseHelperInterface $redirectResponseHelper,
private RedirectResponseHelperInterface $redirectResponseHelper,
?LoggerInterface $logger = null
) {
parent::__construct($urlResolver, $visitTracker, $trackingOptions, $logger);
$this->redirectResponseHelper = $redirectResponseHelper;
}
protected function createSuccessResp(string $longUrl): Response

View file

@ -17,11 +17,8 @@ use const PHP_EOL;
class RobotsAction implements RequestHandlerInterface, StatusCodeInterface
{
private CrawlingHelperInterface $crawlingHelper;
public function __construct(CrawlingHelperInterface $crawlingHelper)
public function __construct(private CrawlingHelperInterface $crawlingHelper)
{
$this->crawlingHelper = $crawlingHelper;
}
public function handle(ServerRequestInterface $request): ResponseInterface

View file

@ -10,11 +10,8 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
class CrawlingHelper implements CrawlingHelperInterface
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
public function __construct(private EntityManagerInterface $em)
{
$this->em = $em;
}
public function listCrawlableShortCodes(): iterable

View file

@ -16,13 +16,8 @@ use function Functional\map;
class DomainService implements DomainServiceInterface
{
private EntityManagerInterface $em;
private string $defaultDomain;
public function __construct(EntityManagerInterface $em, string $defaultDomain)
public function __construct(private EntityManagerInterface $em, private string $defaultDomain)
{
$this->em = $em;
$this->defaultDomain = $defaultDomain;
}
/**
@ -35,7 +30,7 @@ class DomainService implements DomainServiceInterface
$domains = $repo->findDomainsWithout($this->defaultDomain, $apiKey);
$mappedDomains = map($domains, fn (Domain $domain) => new DomainItem($domain->getAuthority(), false));
if ($apiKey !== null && $apiKey->hasRole(Role::DOMAIN_SPECIFIC)) {
if ($apiKey?->hasRole(Role::DOMAIN_SPECIFIC)) {
return $mappedDomains;
}

View file

@ -8,13 +8,8 @@ use JsonSerializable;
final class DomainItem implements JsonSerializable
{
private string $domain;
private bool $isDefault;
public function __construct(string $domain, bool $isDefault)
public function __construct(private string $domain, private bool $isDefault)
{
$this->domain = $domain;
$this->isDefault = $isDefault;
}
public function jsonSerialize(): array

View file

@ -9,11 +9,8 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity;
class Domain extends AbstractEntity implements JsonSerializable
{
private string $authority;
public function __construct(string $authority)
public function __construct(private string $authority)
{
$this->authority = $authority;
}
public function getAuthority(): string

View file

@ -60,7 +60,7 @@ class ShortUrl extends AbstractEntity
public static function fromMeta(
ShortUrlMeta $meta,
?ShortUrlRelationResolverInterface $relationResolver = null
?ShortUrlRelationResolverInterface $relationResolver = null,
): self {
$instance = new self();
$relationResolver = $relationResolver ?? new SimpleShortUrlRelationResolver();
@ -87,7 +87,7 @@ class ShortUrl extends AbstractEntity
public static function fromImport(
ImportedShlinkUrl $url,
bool $importShortCode,
?ShortUrlRelationResolverInterface $relationResolver = null
?ShortUrlRelationResolverInterface $relationResolver = null,
): self {
$meta = [
ShortUrlInputFilter::VALIDATE_URL => false,
@ -209,7 +209,7 @@ class ShortUrl extends AbstractEntity
public function update(
ShortUrlEdit $shortUrlEdit,
?ShortUrlRelationResolverInterface $relationResolver = null
?ShortUrlRelationResolverInterface $relationResolver = null,
): void {
if ($shortUrlEdit->validSinceWasProvided()) {
$this->validSince = $shortUrlEdit->validSince();

View file

@ -10,12 +10,10 @@ use Shlinkio\Shlink\Common\Entity\AbstractEntity;
class Tag extends AbstractEntity implements JsonSerializable
{
private string $name;
private Collections\Collection $shortUrls;
public function __construct(string $name)
public function __construct(private string $name)
{
$this->name = $name;
$this->shortUrls = new Collections\ArrayCollection();
}

View file

@ -104,7 +104,7 @@ class Visit extends AbstractEntity implements JsonSerializable
try {
return (string) IpAddress::fromString($address)->getAnonymizedCopy();
} catch (InvalidArgumentException $e) {
} catch (InvalidArgumentException) {
return null;
}
}

View file

@ -13,31 +13,24 @@ use function rtrim;
class NotFoundType
{
private string $type;
private function __construct(string $type)
private function __construct(private string $type)
{
$this->type = $type;
}
public static function fromRequest(ServerRequestInterface $request, string $basePath): self
{
$isBaseUrl = rtrim($request->getUri()->getPath(), '/') === $basePath;
if ($isBaseUrl) {
return new self(Visit::TYPE_BASE_URL);
}
/** @var RouteResult $routeResult */
$routeResult = $request->getAttribute(RouteResult::class, RouteResult::fromRouteFailure(null));
if ($routeResult->isFailure()) {
return new self(Visit::TYPE_REGULAR_404);
}
$isBaseUrl = rtrim($request->getUri()->getPath(), '/') === $basePath;
if ($routeResult->getMatchedRouteName() === RedirectAction::class) {
return new self(Visit::TYPE_INVALID_SHORT_URL);
}
$type = match (true) {
$isBaseUrl => Visit::TYPE_BASE_URL,
$routeResult->isFailure() => Visit::TYPE_REGULAR_404,
$routeResult->getMatchedRouteName() === RedirectAction::class => Visit::TYPE_INVALID_SHORT_URL,
default => self::class,
};
return new self(self::class);
return new self($type);
}
public function isBaseUrl(): bool

View file

@ -14,15 +14,10 @@ use Shlinkio\Shlink\Core\Util\RedirectResponseHelperInterface;
class NotFoundRedirectHandler implements MiddlewareInterface
{
private Options\NotFoundRedirectOptions $redirectOptions;
private RedirectResponseHelperInterface $redirectResponseHelper;
public function __construct(
Options\NotFoundRedirectOptions $redirectOptions,
RedirectResponseHelperInterface $redirectResponseHelper
private Options\NotFoundRedirectOptions $redirectOptions,
private RedirectResponseHelperInterface $redirectResponseHelper
) {
$this->redirectOptions = $redirectOptions;
$this->redirectResponseHelper = $redirectResponseHelper;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface

View file

@ -20,6 +20,7 @@ class NotFoundTemplateHandler implements RequestHandlerInterface
private const TEMPLATES_BASE_DIR = __DIR__ . '/../../templates';
public const NOT_FOUND_TEMPLATE = '404.html';
public const INVALID_SHORT_CODE_TEMPLATE = 'invalid-short-code.html';
private Closure $readFile;
public function __construct(?callable $readFile = null)

View file

@ -14,11 +14,8 @@ use Shlinkio\Shlink\Core\Visit\VisitsTrackerInterface;
class NotFoundTrackerMiddleware implements MiddlewareInterface
{
private VisitsTrackerInterface $visitsTracker;
public function __construct(VisitsTrackerInterface $visitsTracker)
public function __construct(private VisitsTrackerInterface $visitsTracker)
{
$this->visitsTracker = $visitsTracker;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface

View file

@ -12,11 +12,8 @@ use Shlinkio\Shlink\Core\ErrorHandler\Model\NotFoundType;
class NotFoundTypeResolverMiddleware implements MiddlewareInterface
{
private string $shlinkBasePath;
public function __construct(string $shlinkBasePath)
public function __construct(private string $shlinkBasePath)
{
$this->shlinkBasePath = $shlinkBasePath;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface

View file

@ -8,13 +8,11 @@ use Shlinkio\Shlink\Common\Doctrine\ReopeningEntityManagerInterface;
class CloseDbConnectionEventListener
{
private ReopeningEntityManagerInterface $em;
/** @var callable */
private $wrapped;
public function __construct(ReopeningEntityManagerInterface $em, callable $wrapped)
public function __construct(private ReopeningEntityManagerInterface $em, callable $wrapped)
{
$this->em = $em;
$this->wrapped = $wrapped;
}

View file

@ -12,7 +12,7 @@ class CloseDbConnectionEventListenerDelegator
public function __invoke(
ContainerInterface $container,
string $name,
callable $callback
callable $callback,
): CloseDbConnectionEventListener {
/** @var callable $wrapped */
$wrapped = $callback();

View file

@ -8,11 +8,8 @@ use JsonSerializable;
abstract class AbstractVisitEvent implements JsonSerializable
{
protected string $visitId;
public function __construct(string $visitId)
public function __construct(protected string $visitId)
{
$this->visitId = $visitId;
}
public function visitId(): string

View file

@ -6,12 +6,9 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\Event;
final class UrlVisited extends AbstractVisitEvent
{
private ?string $originalIpAddress;
public function __construct(string $visitId, ?string $originalIpAddress = null)
public function __construct(string $visitId, private ?string $originalIpAddress = null)
{
parent::__construct($visitId);
$this->originalIpAddress = $originalIpAddress;
}
public function originalIpAddress(): ?string

View file

@ -19,24 +19,13 @@ use Throwable;
class LocateVisit
{
private IpLocationResolverInterface $ipLocationResolver;
private EntityManagerInterface $em;
private LoggerInterface $logger;
private DbUpdaterInterface $dbUpdater;
private EventDispatcherInterface $eventDispatcher;
public function __construct(
IpLocationResolverInterface $ipLocationResolver,
EntityManagerInterface $em,
LoggerInterface $logger,
DbUpdaterInterface $dbUpdater,
EventDispatcherInterface $eventDispatcher
private IpLocationResolverInterface $ipLocationResolver,
private EntityManagerInterface $em,
private LoggerInterface $logger,
private DbUpdaterInterface $dbUpdater,
private EventDispatcherInterface $eventDispatcher
) {
$this->ipLocationResolver = $ipLocationResolver;
$this->em = $em;
$this->logger = $logger;
$this->dbUpdater = $dbUpdater;
$this->eventDispatcher = $eventDispatcher;
}
public function __invoke(UrlVisited $shortUrlVisited): void

View file

@ -17,21 +17,12 @@ use function Functional\each;
class NotifyVisitToMercure
{
private HubInterface $hub;
private MercureUpdatesGeneratorInterface $updatesGenerator;
private EntityManagerInterface $em;
private LoggerInterface $logger;
public function __construct(
HubInterface $hub,
MercureUpdatesGeneratorInterface $updatesGenerator,
EntityManagerInterface $em,
LoggerInterface $logger
private HubInterface $hub,
private MercureUpdatesGeneratorInterface $updatesGenerator,
private EntityManagerInterface $em,
private LoggerInterface $logger
) {
$this->hub = $hub;
$this->em = $em;
$this->logger = $logger;
$this->updatesGenerator = $updatesGenerator;
}
public function __invoke(VisitLocated $shortUrlLocated): void

View file

@ -24,28 +24,15 @@ use function Functional\partial_left;
class NotifyVisitToWebHooks
{
private ClientInterface $httpClient;
private EntityManagerInterface $em;
private LoggerInterface $logger;
/** @var string[] */
private array $webhooks;
private DataTransformerInterface $transformer;
private AppOptions $appOptions;
public function __construct(
ClientInterface $httpClient,
EntityManagerInterface $em,
LoggerInterface $logger,
array $webhooks,
DataTransformerInterface $transformer,
AppOptions $appOptions
private ClientInterface $httpClient,
private EntityManagerInterface $em,
private LoggerInterface $logger,
/** @var string[] */
private array $webhooks,
private DataTransformerInterface $transformer,
private AppOptions $appOptions
) {
$this->httpClient = $httpClient;
$this->em = $em;
$this->logger = $logger;
$this->webhooks = $webhooks;
$this->transformer = $transformer;
$this->appOptions = $appOptions;
}
public function __invoke(VisitLocated $shortUrlLocated): void

View file

@ -12,13 +12,8 @@ use function sprintf;
class UpdateGeoLiteDb
{
private GeolocationDbUpdaterInterface $dbUpdater;
private LoggerInterface $logger;
public function __construct(GeolocationDbUpdaterInterface $dbUpdater, LoggerInterface $logger)
public function __construct(private GeolocationDbUpdaterInterface $dbUpdater, private LoggerInterface $logger)
{
$this->dbUpdater = $dbUpdater;
$this->logger = $logger;
}
public function __invoke(): void

View file

@ -20,22 +20,14 @@ use function sprintf;
class ImportedLinksProcessor implements ImportedLinksProcessorInterface
{
private EntityManagerInterface $em;
private ShortUrlRelationResolverInterface $relationResolver;
private ShortCodeHelperInterface $shortCodeHelper;
private DoctrineBatchHelperInterface $batchHelper;
private ShortUrlRepositoryInterface $shortUrlRepo;
public function __construct(
EntityManagerInterface $em,
ShortUrlRelationResolverInterface $relationResolver,
ShortCodeHelperInterface $shortCodeHelper,
DoctrineBatchHelperInterface $batchHelper
private EntityManagerInterface $em,
private ShortUrlRelationResolverInterface $relationResolver,
private ShortCodeHelperInterface $shortCodeHelper,
private DoctrineBatchHelperInterface $batchHelper
) {
$this->em = $em;
$this->relationResolver = $relationResolver;
$this->shortCodeHelper = $shortCodeHelper;
$this->batchHelper = $batchHelper;
$this->shortUrlRepo = $this->em->getRepository(ShortUrl::class); // @phpstan-ignore-line
}
@ -64,7 +56,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
try {
$shortUrlImporting = $this->resolveShortUrl($importedUrl, $importShortCodes, $skipOnShortCodeConflict);
} catch (NonUniqueSlugException $e) {
} catch (NonUniqueSlugException) {
$io->text(sprintf('%s: <fg=red>Error</>', $longUrl));
continue;
}
@ -77,7 +69,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
private function resolveShortUrl(
ImportedShlinkUrl $importedUrl,
bool $importShortCodes,
callable $skipOnShortCodeConflict
callable $skipOnShortCodeConflict,
): ShortUrlImporting {
$alreadyImportedShortUrl = $this->shortUrlRepo->findOneByImportedUrl($importedUrl);
if ($alreadyImportedShortUrl !== null) {
@ -96,7 +88,7 @@ class ImportedLinksProcessor implements ImportedLinksProcessorInterface
private function handleShortCodeUniqueness(
ShortUrl $shortUrl,
bool $importShortCodes,
callable $skipOnShortCodeConflict
callable $skipOnShortCodeConflict,
): bool {
if ($this->shortCodeHelper->ensureShortCodeUniqueness($shortUrl, $importShortCodes)) {
return true;

View file

@ -14,13 +14,8 @@ use function sprintf;
final class ShortUrlImporting
{
private ShortUrl $shortUrl;
private bool $isNew;
private function __construct(ShortUrl $shortUrl, bool $isNew)
private function __construct(private ShortUrl $shortUrl, private bool $isNew)
{
$this->shortUrl = $shortUrl;
$this->isNew = $isNew;
}
public static function fromExistingShortUrl(ShortUrl $shortUrl): self
@ -43,10 +38,7 @@ final class ShortUrlImporting
$importedVisits = 0;
foreach ($visits as $importedVisit) {
// Skip visits which are older than the most recent already imported visit's date
if (
$mostRecentImportedDate !== null
&& $mostRecentImportedDate->gte(Chronos::instance($importedVisit->date()))
) {
if ($mostRecentImportedDate?->gte(Chronos::instance($importedVisit->date()))) {
continue;
}

View file

@ -18,15 +18,10 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface
private const NEW_VISIT_TOPIC = 'https://shlink.io/new-visit';
private const NEW_ORPHAN_VISIT_TOPIC = 'https://shlink.io/new-orphan-visit';
private DataTransformerInterface $shortUrlTransformer;
private DataTransformerInterface $orphanVisitTransformer;
public function __construct(
DataTransformerInterface $shortUrlTransformer,
DataTransformerInterface $orphanVisitTransformer
private DataTransformerInterface $shortUrlTransformer,
private DataTransformerInterface $orphanVisitTransformer
) {
$this->shortUrlTransformer = $shortUrlTransformer;
$this->orphanVisitTransformer = $orphanVisitTransformer;
}
public function newVisitUpdate(Visit $visit): Update

View file

@ -10,13 +10,8 @@ use Symfony\Component\Console\Input\InputInterface;
final class ShortUrlIdentifier
{
private string $shortCode;
private ?string $domain;
public function __construct(string $shortCode, ?string $domain = null)
public function __construct(private string $shortCode, private ?string $domain = null)
{
$this->shortCode = $shortCode;
$this->domain = $domain;
}
public static function fromApiRequest(ServerRequestInterface $request): self
@ -46,7 +41,7 @@ final class ShortUrlIdentifier
public static function fromShortUrl(ShortUrl $shortUrl): self
{
$domain = $shortUrl->getDomain();
$domainAuthority = $domain !== null ? $domain->getAuthority() : null;
$domainAuthority = $domain?->getAuthority();
return new self($shortUrl->getShortCode(), $domainAuthority);
}

View file

@ -14,20 +14,16 @@ final class VisitsParams
private const ALL_ITEMS = -1;
private ?DateRange $dateRange;
private int $page;
private int $itemsPerPage;
private bool $excludeBots;
public function __construct(
?DateRange $dateRange = null,
int $page = self::FIRST_PAGE,
private int $page = self::FIRST_PAGE,
?int $itemsPerPage = null,
bool $excludeBots = false
private bool $excludeBots = false
) {
$this->dateRange = $dateRange ?? new DateRange();
$this->page = $page;
$this->itemsPerPage = $this->determineItemsPerPage($itemsPerPage);
$this->excludeBots = $excludeBots;
}
private function determineItemsPerPage(?int $itemsPerPage): int

View file

@ -11,13 +11,8 @@ use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
{
private VisitRepositoryInterface $repo;
private VisitsParams $params;
public function __construct(VisitRepositoryInterface $repo, VisitsParams $params)
public function __construct(private VisitRepositoryInterface $repo, private VisitsParams $params)
{
$this->repo = $repo;
$this->params = $params;
}
protected function doCount(): int

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
use Happyr\DoctrineSpecification\Specification\Specification;
use Pagerfanta\Adapter\AdapterInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
@ -12,15 +11,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlRepositoryAdapter implements AdapterInterface
{
private ShortUrlRepositoryInterface $repository;
private ShortUrlsParams $params;
private ?ApiKey $apiKey;
public function __construct(ShortUrlRepositoryInterface $repository, ShortUrlsParams $params, ?ApiKey $apiKey)
{
$this->repository = $repository;
$this->params = $params;
$this->apiKey = $apiKey;
public function __construct(
private ShortUrlRepositoryInterface $repository,
private ShortUrlsParams $params,
private ?ApiKey $apiKey
) {
}
public function getSlice($offset, $length): array // phpcs:ignore
@ -32,7 +27,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
$this->params->tags(),
$this->params->orderBy(),
$this->params->dateRange(),
$this->resolveSpec(),
$this->apiKey?->spec(),
);
}
@ -42,12 +37,7 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
$this->params->searchTerm(),
$this->params->tags(),
$this->params->dateRange(),
$this->resolveSpec(),
$this->apiKey?->spec(),
);
}
private function resolveSpec(): ?Specification
{
return $this->apiKey !== null ? $this->apiKey->spec() : null;
}
}

View file

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Core\Model\VisitsParams;
use Shlinkio\Shlink\Core\Repository\VisitRepositoryInterface;
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
@ -13,21 +12,12 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class VisitsForTagPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
{
private VisitRepositoryInterface $visitRepository;
private string $tag;
private VisitsParams $params;
private ?ApiKey $apiKey;
public function __construct(
VisitRepositoryInterface $visitRepository,
string $tag,
VisitsParams $params,
?ApiKey $apiKey
private VisitRepositoryInterface $visitRepository,
private string $tag,
private VisitsParams $params,
private ?ApiKey $apiKey
) {
$this->visitRepository = $visitRepository;
$this->params = $params;
$this->tag = $tag;
$this->apiKey = $apiKey;
}
public function getSlice($offset, $length): array // phpcs:ignore
@ -37,7 +27,7 @@ class VisitsForTagPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
new VisitsListFiltering(
$this->params->getDateRange(),
$this->params->excludeBots(),
$this->resolveSpec(),
$this->apiKey?->spec(true),
$length,
$offset,
),
@ -51,13 +41,8 @@ class VisitsForTagPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
new VisitsCountFiltering(
$this->params->getDateRange(),
$this->params->excludeBots(),
$this->resolveSpec(),
$this->apiKey?->spec(true),
),
);
}
private function resolveSpec(): ?Specification
{
return $this->apiKey !== null ? $this->apiKey->spec(true) : null;
}
}

View file

@ -13,21 +13,12 @@ use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
class VisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
{
private VisitRepositoryInterface $visitRepository;
private ShortUrlIdentifier $identifier;
private VisitsParams $params;
private ?Specification $spec;
public function __construct(
VisitRepositoryInterface $visitRepository,
ShortUrlIdentifier $identifier,
VisitsParams $params,
?Specification $spec
private VisitRepositoryInterface $visitRepository,
private ShortUrlIdentifier $identifier,
private VisitsParams $params,
private ?Specification $spec
) {
$this->visitRepository = $visitRepository;
$this->params = $params;
$this->identifier = $identifier;
$this->spec = $spec;
}
public function getSlice($offset, $length): array // phpcs:ignore

View file

@ -35,7 +35,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
array $tags = [],
?ShortUrlsOrdering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null
?Specification $spec = null,
): array {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec);
$qb->select('DISTINCT s')
@ -43,7 +43,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
->setFirstResult($offset);
// In case the ordering has been specified, the query could be more complex. Process it
if ($orderBy !== null && $orderBy->hasOrderField()) {
if ($orderBy?->hasOrderField()) {
return $this->processOrderByForList($qb, $orderBy);
}
@ -85,7 +85,7 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
?string $searchTerm = null,
array $tags = [],
?DateRange $dateRange = null,
?Specification $spec = null
?Specification $spec = null,
): int {
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange, $spec);
$qb->select('COUNT(DISTINCT s)');
@ -97,17 +97,17 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
?string $searchTerm,
array $tags,
?DateRange $dateRange,
?Specification $spec
?Specification $spec,
): QueryBuilder {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->from(ShortUrl::class, 's')
->where('1=1');
if ($dateRange !== null && $dateRange->getStartDate() !== null) {
if ($dateRange?->getStartDate() !== null) {
$qb->andWhere($qb->expr()->gte('s.dateCreated', ':startDate'));
$qb->setParameter('startDate', $dateRange->getStartDate(), ChronosDateTimeType::CHRONOS_DATETIME);
}
if ($dateRange !== null && $dateRange->getEndDate() !== null) {
if ($dateRange?->getEndDate() !== null) {
$qb->andWhere($qb->expr()->lte('s.dateCreated', ':endDate'));
$qb->setParameter('endDate', $dateRange->getEndDate(), ChronosDateTimeType::CHRONOS_DATETIME);
}

View file

@ -23,14 +23,14 @@ interface ShortUrlRepositoryInterface extends ObjectRepository, EntitySpecificat
array $tags = [],
?ShortUrlsOrdering $orderBy = null,
?DateRange $dateRange = null,
?Specification $spec = null
?Specification $spec = null,
): array;
public function countList(
?string $searchTerm = null,
array $tags = [],
?DateRange $dateRange = null,
?Specification $spec = null
?Specification $spec = null,
): int;
public function findOneWithDomainFallback(string $shortCode, ?string $domain = null): ?ShortUrl;

View file

@ -71,14 +71,14 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
$iterator = $qb->getQuery()->toIterable();
$resultsFound = false;
/** @var Visit $visit */
foreach ($iterator as $key => $visit) {
$resultsFound = true;
yield $key => $visit;
}
// As the query is ordered by ID, we can take the last one every time in order to exclude the whole list
$lastId = isset($visit) ? $visit->getId() : $lastId;
/** @var Visit|null $visit */
$lastId = $visit?->getId() ?? $lastId;
} while ($resultsFound);
}
@ -101,12 +101,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
private function createVisitsByShortCodeQueryBuilder(
ShortUrlIdentifier $identifier,
VisitsCountFiltering $filtering
VisitsCountFiltering $filtering,
): QueryBuilder {
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
$shortUrlRepo = $this->getEntityManager()->getRepository(ShortUrl::class);
$shortUrl = $shortUrlRepo->findOne($identifier, $filtering->spec());
$shortUrlId = $shortUrl !== null ? $shortUrl->getId() : -1;
$shortUrlId = $shortUrl?->getId() ?? '-1';
// Parameters in this query need to be part of the query itself, as we need to use it a sub-query later
// Since they are not strictly provided by the caller, it's reasonably safe
@ -187,10 +187,10 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
private function applyDatesInline(QueryBuilder $qb, ?DateRange $dateRange): void
{
if ($dateRange !== null && $dateRange->getStartDate() !== null) {
if ($dateRange?->getStartDate() !== null) {
$qb->andWhere($qb->expr()->gte('v.date', '\'' . $dateRange->getStartDate()->toDateTimeString() . '\''));
}
if ($dateRange !== null && $dateRange->getEndDate() !== null) {
if ($dateRange?->getEndDate() !== null) {
$qb->andWhere($qb->expr()->lte('v.date', '\'' . $dateRange->getEndDate()->toDateTimeString() . '\''));
}
}

View file

@ -13,18 +13,11 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class DeleteShortUrlService implements DeleteShortUrlServiceInterface
{
private EntityManagerInterface $em;
private DeleteShortUrlsOptions $deleteShortUrlsOptions;
private ShortUrlResolverInterface $urlResolver;
public function __construct(
EntityManagerInterface $em,
DeleteShortUrlsOptions $deleteShortUrlsOptions,
ShortUrlResolverInterface $urlResolver
private EntityManagerInterface $em,
private DeleteShortUrlsOptions $deleteShortUrlsOptions,
private ShortUrlResolverInterface $urlResolver
) {
$this->em = $em;
$this->deleteShortUrlsOptions = $deleteShortUrlsOptions;
$this->urlResolver = $urlResolver;
}
/**
@ -34,7 +27,7 @@ class DeleteShortUrlService implements DeleteShortUrlServiceInterface
public function deleteByShortCode(
ShortUrlIdentifier $identifier,
bool $ignoreThreshold = false,
?ApiKey $apiKey = null
?ApiKey $apiKey = null,
): void {
$shortUrl = $this->urlResolver->resolveShortUrl($identifier, $apiKey);
if (! $ignoreThreshold && $this->isThresholdReached($shortUrl)) {

View file

@ -17,6 +17,6 @@ interface DeleteShortUrlServiceInterface
public function deleteByShortCode(
ShortUrlIdentifier $identifier,
bool $ignoreThreshold = false,
?ApiKey $apiKey = null
?ApiKey $apiKey = null,
): void;
}

View file

@ -11,11 +11,8 @@ use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
class ShortCodeHelper implements ShortCodeHelperInterface // TODO Rename to ShortCodeUniquenessHelper
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
public function __construct(private EntityManagerInterface $em)
{
$this->em = $em;
}
public function ensureShortCodeUniqueness(ShortUrl $shortUrlToBeCreated, bool $hasCustomSlug): bool

View file

@ -13,11 +13,8 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlResolver implements ShortUrlResolverInterface
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
public function __construct(private EntityManagerInterface $em)
{
$this->em = $em;
}
/**
@ -27,7 +24,7 @@ class ShortUrlResolver implements ShortUrlResolverInterface
{
/** @var ShortUrlRepository $shortUrlRepo */
$shortUrlRepo = $this->em->getRepository(ShortUrl::class);
$shortUrl = $shortUrlRepo->findOne($identifier, $apiKey !== null ? $apiKey->spec() : null);
$shortUrl = $shortUrlRepo->findOne($identifier, $apiKey?->spec());
if ($shortUrl === null) {
throw ShortUrlNotFoundException::fromNotFound($identifier);
}
@ -43,7 +40,7 @@ class ShortUrlResolver implements ShortUrlResolverInterface
/** @var ShortUrlRepository $shortUrlRepo */
$shortUrlRepo = $this->em->getRepository(ShortUrl::class);
$shortUrl = $shortUrlRepo->findOneWithDomainFallback($identifier->shortCode(), $identifier->domain());
if ($shortUrl === null || ! $shortUrl->isEnabled()) {
if (! $shortUrl?->isEnabled()) {
throw ShortUrlNotFoundException::fromNotFound($identifier);
}

View file

@ -21,21 +21,12 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class ShortUrlService implements ShortUrlServiceInterface
{
private ORM\EntityManagerInterface $em;
private ShortUrlResolverInterface $urlResolver;
private ShortUrlTitleResolutionHelperInterface $titleResolutionHelper;
private ShortUrlRelationResolverInterface $relationResolver;
public function __construct(
ORM\EntityManagerInterface $em,
ShortUrlResolverInterface $urlResolver,
ShortUrlTitleResolutionHelperInterface $titleResolutionHelper,
ShortUrlRelationResolverInterface $relationResolver
private ORM\EntityManagerInterface $em,
private ShortUrlResolverInterface $urlResolver,
private ShortUrlTitleResolutionHelperInterface $titleResolutionHelper,
private ShortUrlRelationResolverInterface $relationResolver
) {
$this->em = $em;
$this->urlResolver = $urlResolver;
$this->titleResolutionHelper = $titleResolutionHelper;
$this->relationResolver = $relationResolver;
}
/**
@ -59,7 +50,7 @@ class ShortUrlService implements ShortUrlServiceInterface
public function updateShortUrl(
ShortUrlIdentifier $identifier,
ShortUrlEdit $shortUrlEdit,
?ApiKey $apiKey = null
?ApiKey $apiKey = null,
): ShortUrl {
if ($shortUrlEdit->longUrlWasProvided()) {
/** @var ShortUrlEdit $shortUrlEdit */

View file

@ -27,6 +27,6 @@ interface ShortUrlServiceInterface
public function updateShortUrl(
ShortUrlIdentifier $identifier,
ShortUrlEdit $shortUrlEdit,
?ApiKey $apiKey = null
?ApiKey $apiKey = null,
): ShortUrl;
}

View file

@ -16,21 +16,12 @@ use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface;
class UrlShortener implements UrlShortenerInterface
{
private EntityManagerInterface $em;
private ShortUrlTitleResolutionHelperInterface $titleResolutionHelper;
private ShortUrlRelationResolverInterface $relationResolver;
private ShortCodeHelperInterface $shortCodeHelper;
public function __construct(
ShortUrlTitleResolutionHelperInterface $titleResolutionHelper,
EntityManagerInterface $em,
ShortUrlRelationResolverInterface $relationResolver,
ShortCodeHelperInterface $shortCodeHelper
private ShortUrlTitleResolutionHelperInterface $titleResolutionHelper,
private EntityManagerInterface $em,
private ShortUrlRelationResolverInterface $relationResolver,
private ShortCodeHelperInterface $shortCodeHelper
) {
$this->titleResolutionHelper = $titleResolutionHelper;
$this->em = $em;
$this->relationResolver = $relationResolver;
$this->shortCodeHelper = $shortCodeHelper;
}
/**
@ -78,7 +69,7 @@ class UrlShortener implements UrlShortenerInterface
if (! $couldBeMadeUnique) {
$domain = $shortUrlToBeCreated->getDomain();
$domainAuthority = $domain !== null ? $domain->getAuthority() : null;
$domainAuthority = $domain?->getAuthority();
throw NonUniqueSlugException::fromSlug($shortUrlToBeCreated->getShortCode(), $domainAuthority);
}

View file

@ -11,13 +11,8 @@ use function sprintf;
class ShortUrlStringifier implements ShortUrlStringifierInterface
{
private array $domainConfig;
private string $basePath;
public function __construct(array $domainConfig, string $basePath = '')
public function __construct(private array $domainConfig, private string $basePath = '')
{
$this->domainConfig = $domainConfig;
$this->basePath = $basePath;
}
public function stringify(ShortUrl $shortUrl): string

View file

@ -8,11 +8,8 @@ use Shlinkio\Shlink\Core\Util\UrlValidatorInterface;
class ShortUrlTitleResolutionHelper implements ShortUrlTitleResolutionHelperInterface
{
private UrlValidatorInterface $urlValidator;
public function __construct(UrlValidatorInterface $urlValidator)
public function __construct(private UrlValidatorInterface $urlValidator)
{
$this->urlValidator = $urlValidator;
}
public function processTitleAndValidateUrl(TitleResolutionModelInterface $data): TitleResolutionModelInterface

View file

@ -16,16 +16,13 @@ use function Functional\unique;
class PersistenceShortUrlRelationResolver implements ShortUrlRelationResolverInterface
{
private EntityManagerInterface $em;
/** @var array<string, Domain> */
private array $memoizedNewDomains = [];
/** @var array<string, Tag> */
private array $memoizedNewTags = [];
public function __construct(EntityManagerInterface $em)
public function __construct(private EntityManagerInterface $em)
{
$this->em = $em;
$this->em->getEventManager()->addEventListener(Events::postFlush, $this);
}

View file

@ -11,13 +11,8 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class BelongsToApiKey extends BaseSpecification
{
private ApiKey $apiKey;
private ?string $dqlAlias;
public function __construct(ApiKey $apiKey, ?string $dqlAlias = null)
public function __construct(private ApiKey $apiKey, private ?string $dqlAlias = null)
{
$this->apiKey = $apiKey;
$this->dqlAlias = $dqlAlias;
parent::__construct();
}

View file

@ -10,11 +10,8 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
class BelongsToApiKeyInlined implements Filter
{
private ApiKey $apiKey;
public function __construct(ApiKey $apiKey)
public function __construct(private ApiKey $apiKey)
{
$this->apiKey = $apiKey;
}
public function getFilter(QueryBuilder $qb, string $dqlAlias): string

View file

@ -10,13 +10,8 @@ use Happyr\DoctrineSpecification\Specification\BaseSpecification;
class BelongsToDomain extends BaseSpecification
{
private string $domainId;
private ?string $dqlAlias;
public function __construct(string $domainId, ?string $dqlAlias = null)
public function __construct(private string $domainId, private ?string $dqlAlias = null)
{
$this->domainId = $domainId;
$this->dqlAlias = $dqlAlias;
parent::__construct();
}

View file

@ -9,11 +9,8 @@ use Happyr\DoctrineSpecification\Filter\Filter;
class BelongsToDomainInlined implements Filter
{
private string $domainId;
public function __construct(string $domainId)
public function __construct(private string $domainId)
{
$this->domainId = $domainId;
}
public function getFilter(QueryBuilder $qb, string $dqlAlias): string

View file

@ -13,11 +13,8 @@ use function Functional\invoke_if;
class ShortUrlDataTransformer implements DataTransformerInterface
{
private ShortUrlStringifierInterface $stringifier;
public function __construct(ShortUrlStringifierInterface $stringifier)
public function __construct(private ShortUrlStringifierInterface $stringifier)
{
$this->stringifier = $stringifier;
}
/**

View file

@ -11,25 +11,20 @@ use Shlinkio\Shlink\Common\Util\DateRange;
class InDateRange extends BaseSpecification
{
private ?DateRange $dateRange;
private string $field;
public function __construct(?DateRange $dateRange, string $field = 'date')
public function __construct(private ?DateRange $dateRange, private string $field = 'date')
{
parent::__construct();
$this->dateRange = $dateRange;
$this->field = $field;
}
protected function getSpec(): Specification
{
$criteria = [];
if ($this->dateRange !== null && $this->dateRange->getStartDate() !== null) {
if ($this->dateRange?->getStartDate() !== null) {
$criteria[] = Spec::gte($this->field, $this->dateRange->getStartDate()->toDateTimeString());
}
if ($this->dateRange !== null && $this->dateRange->getEndDate() !== null) {
if ($this->dateRange?->getEndDate() !== null) {
$criteria[] = Spec::lte($this->field, $this->dateRange->getEndDate()->toDateTimeString());
}

View file

@ -9,15 +9,8 @@ use Shlinkio\Shlink\Core\Entity\Tag;
final class TagInfo implements JsonSerializable
{
private Tag $tag;
private int $shortUrlsCount;
private int $visitsCount;
public function __construct(Tag $tag, int $shortUrlsCount, int $visitsCount)
public function __construct(private Tag $tag, private int $shortUrlsCount, private int $visitsCount)
{
$this->tag = $tag;
$this->shortUrlsCount = $shortUrlsCount;
$this->visitsCount = $visitsCount;
}
public function tag(): Tag

View file

@ -10,12 +10,9 @@ use Happyr\DoctrineSpecification\Specification\Specification;
class CountTagsWithName extends BaseSpecification
{
private string $tagName;
public function __construct(string $tagName)
public function __construct(private string $tagName)
{
parent::__construct();
$this->tagName = $tagName;
}
protected function getSpec(): Specification

View file

@ -23,11 +23,8 @@ class TagService implements TagServiceInterface
{
use TagManagerTrait;
private ORM\EntityManagerInterface $em;
public function __construct(ORM\EntityManagerInterface $em)
public function __construct(private ORM\EntityManagerInterface $em)
{
$this->em = $em;
}
/**

View file

@ -12,11 +12,8 @@ use function Symfony\Component\String\s;
class CocurSymfonySluggerBridge implements SluggerInterface
{
private SlugifyInterface $slugger;
public function __construct(SlugifyInterface $slugger)
public function __construct(private SlugifyInterface $slugger)
{
$this->slugger = $slugger;
}
public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString

View file

@ -12,11 +12,8 @@ use Throwable;
*/
class DoctrineBatchHelper implements DoctrineBatchHelperInterface
{
private EntityManagerInterface $em;
public function __construct(EntityManagerInterface $em)
public function __construct(private EntityManagerInterface $em)
{
$this->em = $em;
}
/**

View file

@ -13,11 +13,8 @@ use function sprintf;
class RedirectResponseHelper implements RedirectResponseHelperInterface
{
private UrlShortenerOptions $options;
public function __construct(UrlShortenerOptions $options)
public function __construct(private UrlShortenerOptions $options)
{
$this->options = $options;
}
public function buildRedirectResponse(string $location): ResponseInterface

View file

@ -23,13 +23,8 @@ class UrlValidator implements UrlValidatorInterface, RequestMethodInterface
private const CHROME_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
. 'Chrome/51.0.2704.103 Safari/537.36';
private ClientInterface $httpClient;
private UrlShortenerOptions $options;
public function __construct(ClientInterface $httpClient, UrlShortenerOptions $options)
public function __construct(private ClientInterface $httpClient, private UrlShortenerOptions $options)
{
$this->httpClient = $httpClient;
$this->options = $options;
}
/**

View file

@ -8,13 +8,8 @@ use JsonSerializable;
final class VisitsStats implements JsonSerializable
{
private int $visitsCount;
private int $orphanVisitsCount;
public function __construct(int $visitsCount, int $orphanVisitsCount)
public function __construct(private int $visitsCount, private int $orphanVisitsCount)
{
$this->visitsCount = $visitsCount;
$this->orphanVisitsCount = $orphanVisitsCount;
}
public function jsonSerialize(): array

View file

@ -9,15 +9,11 @@ use Shlinkio\Shlink\Common\Util\DateRange;
class VisitsCountFiltering
{
private ?DateRange $dateRange;
private bool $excludeBots;
private ?Specification $spec;
public function __construct(?DateRange $dateRange = null, bool $excludeBots = false, ?Specification $spec = null)
{
$this->dateRange = $dateRange;
$this->excludeBots = $excludeBots;
$this->spec = $spec;
public function __construct(
private ?DateRange $dateRange = null,
private bool $excludeBots = false,
private ?Specification $spec = null
) {
}
public function dateRange(): ?DateRange

Some files were not shown because too many files have changed in this diff Show more