From b51fa4ddad8bfc708bb4a3f702b9419990bc06b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Mon, 26 Oct 2026 10:59:53 +0100 Subject: [PATCH] refactor: PHP 8.5 language features via Rector rector.php gets the PHP sets up to 8.5. Mostly constructor promotion with readonly properties, typed class constants, ??=, arrow functions, str_contains/str_starts_with, catch without unused variables, array_any and #[\Override] on overridden properties; array_key_exists() keys are cast to string (null keys are deprecated in PHP 8.5). Long promoted constructor signatures are wrapped one parameter per line. Checked locally: a crawl of ~1,900 frontend URLs over all sites and the backchannel APIs (/api/hotel, /api/product, content feed manifest and records of every type) render the same as before. Co-Authored-By: Claude Opus 5.5 --- .../Classes/ContentFeed/ContentFeedApi.php | 44 +---- .../Classes/ContentFeed/ManifestBuilder.php | 60 ++---- .../Classes/ContentFeed/PageTree.php | 10 +- .../Classes/ContentFeed/RecordFinder.php | 8 +- .../Classes/ContentFeed/RecordSerializer.php | 180 ++++++++---------- .../Classes/ContentFeed/RteLinkResolver.php | 18 +- .../Classes/ContentFeed/UrlResolver.php | 37 ++-- .../Controller/AjaxCalendarController.php | 12 +- .../Controller/AjaxFilterPanelController.php | 8 +- .../Controller/AjaxTableController.php | 12 +- .../Classes/Controller/CityController.php | 8 +- .../Classes/Controller/ConceptController.php | 13 +- .../Controller/NewsletterController.php | 16 +- .../Classes/Controller/ProductController.php | 18 +- .../Classes/Controller/RegionController.php | 4 +- .../Classes/Controller/SearchController.php | 10 +- .../Controller/TravelinfoController.php | 30 +-- .../Classes/Domain/Model/Country.php | 1 + .../Dto/NewsletterSubscriptionRequest.php | 4 +- .../Classes/Domain/Model/SearchResult.php | 8 +- .../Domain/Repository/CityRepository.php | 12 +- .../Domain/Repository/RegionRepository.php | 4 +- .../Repository/TeammemberRepository.php | 1 + .../ep_products/Classes/EpTeam/ApiClient.php | 20 +- .../Classes/Http/ApiKeyAuthenticator.php | 8 +- .../Classes/Middleware/ApiMiddleware.php | 70 ++----- .../ContentPageTypeGuardMiddleware.php | 18 +- .../StaticSearchParamsMiddleware.php | 2 +- .../ep_products/Classes/MyEP/ApiClient.php | 47 +++-- .../Classes/Service/ApiImportSource.php | 22 +-- .../Classes/Service/DateImportService.php | 22 +-- .../Classes/Service/DateService.php | 36 ++-- .../Classes/Service/FilterService.php | 18 +- .../Classes/Service/GroupsSwissService.php | 8 +- .../Classes/Service/HotelDataService.php | 2 +- .../Classes/Service/HotelImageService.php | 1 + .../Classes/Service/PickupOverviewService.php | 8 +- .../Classes/Service/ProductImageService.php | 1 + .../Classes/Service/ProductImport.php | 27 +-- .../Classes/Service/RoomTypeResolver.php | 8 +- .../Classes/Service/SearchResultService.php | 4 +- .../Service/SnowreportImportService.php | 26 +-- .../Classes/Utility/TypeConversionUtility.php | 4 +- .../Configuration/RequestMiddlewares.php | 3 +- packages/ep_products/ext_localconf.php | 3 +- .../ep_theme/Classes/Domain/Model/Page.php | 2 + .../Domain/Repository/PageRepository.php | 1 + .../Classes/Form/AbstractMappedFinisher.php | 6 +- .../Classes/Form/ApplicationApiFinisher.php | 2 +- .../Classes/Middleware/CdnImageMiddleware.php | 6 +- .../ViewHelpers/ContainerViewHelper.php | 5 +- .../FilterSettingsEncoderViewHelper.php | 5 +- .../ViewHelpers/GmapsUrlViewHelper.php | 1 + .../Classes/ViewHelpers/IconViewHelper.php | 1 + .../Classes/ViewHelpers/RatingViewHelper.php | 5 +- .../ViewHelpers/ThemeClassesViewHelper.php | 2 +- .../ViewHelpers/Uri/VideoViewHelper.php | 1 + .../Configuration/RequestMiddlewares.php | 10 +- .../tt_content_element_container.php | 2 +- .../TCA/tx_eptheme_domain_model_lineup.php | 3 +- rector.php | 1 + 61 files changed, 320 insertions(+), 609 deletions(-) diff --git a/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php index 230fd82..af21030 100644 --- a/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php +++ b/packages/ep_products/Classes/ContentFeed/ContentFeedApi.php @@ -19,53 +19,29 @@ class ContentFeedApi { public const PATH_PREFIX = '/api/content/'; - private const MANIFEST_CACHE_IDENTIFIER = 'content_feed_manifest'; + private const string MANIFEST_CACHE_IDENTIFIER = 'content_feed_manifest'; // The consumer polls every 15 minutes, so this caps the build at once per poll however // often it retries. Edits reach the chatbot within about 25 minutes. - private const MANIFEST_CACHE_LIFETIME = 600; + private const int MANIFEST_CACHE_LIFETIME = 600; - private const MAX_RECORDS_PER_REQUEST = 100; + private const int MAX_RECORDS_PER_REQUEST = 100; - private const DISABLED_RETRY_AFTER = 3600; + private const int DISABLED_RETRY_AFTER = 3600; private const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; - /** - * @var ApiKeyAuthenticator - */ - private $authenticator; - - /** - * @var ContentFeedConfiguration - */ - private $configuration; - - /** - * @var ManifestBuilder - */ - private $manifestBuilder; - - /** - * @var RecordSerializer - */ - private $recordSerializer; - /** * @var FrontendInterface */ private $cache; public function __construct( - ApiKeyAuthenticator $authenticator, - ContentFeedConfiguration $configuration, - ManifestBuilder $manifestBuilder, - RecordSerializer $recordSerializer, + private readonly ApiKeyAuthenticator $authenticator, + private readonly ContentFeedConfiguration $configuration, + private readonly ManifestBuilder $manifestBuilder, + private readonly RecordSerializer $recordSerializer, CacheManager $cacheManager ) { - $this->authenticator = $authenticator; - $this->configuration = $configuration; - $this->manifestBuilder = $manifestBuilder; - $this->recordSerializer = $recordSerializer; $this->cache = $cacheManager->getCache('ep_products_cache'); } @@ -112,9 +88,7 @@ class ContentFeedApi $uids = array_values(array_unique(array_filter( GeneralUtility::intExplode(',', (string) ($request->getQueryParams()['uids'] ?? ''), true), - function (int $uid): bool { - return $uid > 0; - } + fn(int $uid): bool => $uid > 0 ))); if ([] === $uids) { diff --git a/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php index 20805e9..48b1088 100644 --- a/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php +++ b/packages/ep_products/Classes/ContentFeed/ManifestBuilder.php @@ -16,13 +16,13 @@ class ManifestBuilder { public const CONTENT_PAGE_TYPE = 1730; - private const DOKTYPE_DEFAULT = 1; + private const int DOKTYPE_DEFAULT = 1; // Recycler and backend user section: nothing below them is public. - private const DOKTYPES_NOT_TRAVERSED = [6, 255]; + private const array DOKTYPES_NOT_TRAVERSED = [6, 255]; // l18n_cfg bit 1: "hide default translation of page". - private const L18N_HIDE_DEFAULT_LANGUAGE = 1; + private const int L18N_HIDE_DEFAULT_LANGUAGE = 1; - private const RECORD_FIELDS = [ + private const array RECORD_FIELDS = [ 'product' => ['uid', 'tstamp', 'detail_page'], 'hotel' => ['uid', 'tstamp', 'detail_page'], 'region' => ['uid', 'tstamp', 'detail_page'], @@ -32,43 +32,13 @@ class ManifestBuilder 'news' => ['uid', 'tstamp', 'pid'], ]; - /** - * @var PageTree - */ - private $pageTree; - - /** - * @var UrlResolver - */ - private $urlResolver; - - /** - * @var RecordFinder - */ - private $recordFinder; - - /** - * @var ContentFeedConfiguration - */ - private $configuration; - - /** - * @var ConnectionPool - */ - private $connectionPool; - public function __construct( - PageTree $pageTree, - UrlResolver $urlResolver, - RecordFinder $recordFinder, - ContentFeedConfiguration $configuration, - ConnectionPool $connectionPool + private readonly PageTree $pageTree, + private readonly UrlResolver $urlResolver, + private readonly RecordFinder $recordFinder, + private readonly ContentFeedConfiguration $configuration, + private readonly ConnectionPool $connectionPool, ) { - $this->pageTree = $pageTree; - $this->urlResolver = $urlResolver; - $this->recordFinder = $recordFinder; - $this->configuration = $configuration; - $this->connectionPool = $connectionPool; } public function build(): array @@ -78,7 +48,7 @@ class ManifestBuilder return [ 'generatedAt' => date('c'), - 'sites' => array_map([$this, 'describeSite'], $sites), + 'sites' => array_map($this->describeSite(...), $sites), 'items' => array_merge($this->buildPageItems($sites, $this->collectDetailPages()), $records), ]; } @@ -211,8 +181,14 @@ class ManifestBuilder return 0 !== $languageId || 0 === ((int) $page['l18n_cfg'] & self::L18N_HIDE_DEFAULT_LANGUAGE); } - private function buildPageItem(array $page, array $row, int $languageId, string $isoCode, array $breadcrumb, array $context): ?array - { + private function buildPageItem( + array $page, + array $row, + int $languageId, + string $isoCode, + array $breadcrumb, + array $context, + ): ?array { $uid = (int) $page['uid']; // "Show content from page": the elements live on another page, and so do their changes. // The site answers 404 when that page is hidden. diff --git a/packages/ep_products/Classes/ContentFeed/PageTree.php b/packages/ep_products/Classes/ContentFeed/PageTree.php index 60844a5..b7d96c2 100644 --- a/packages/ep_products/Classes/ContentFeed/PageTree.php +++ b/packages/ep_products/Classes/ContentFeed/PageTree.php @@ -15,17 +15,12 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; */ class PageTree { - private const FIELDS = [ + private const array FIELDS = [ 'uid', 'pid', 'sorting', 'doktype', 'hidden', 'starttime', 'endtime', 'fe_group', 'extendToSubpages', 'no_index', 'title', 'nav_title', 'description', 'shortcut', 'shortcut_mode', 'content_from_pid', 'tstamp', 'sys_language_uid', 'l10n_parent', 'l18n_cfg', ]; - /** - * @var ConnectionPool - */ - private $connectionPool; - /** * @var int */ @@ -46,9 +41,8 @@ class PageTree */ private $overlays = []; - public function __construct(ConnectionPool $connectionPool, Context $context) + public function __construct(private readonly ConnectionPool $connectionPool, Context $context) { - $this->connectionPool = $connectionPool; $this->now = (int) $context->getPropertyFromAspect('date', 'timestamp'); } diff --git a/packages/ep_products/Classes/ContentFeed/RecordFinder.php b/packages/ep_products/Classes/ContentFeed/RecordFinder.php index 9e99fc9..5036e80 100644 --- a/packages/ep_products/Classes/ContentFeed/RecordFinder.php +++ b/packages/ep_products/Classes/ContentFeed/RecordFinder.php @@ -26,14 +26,8 @@ class RecordFinder 'news' => 'tx_news_domain_model_news', ]; - /** - * @var ConnectionPool - */ - private $connectionPool; - - public function __construct(ConnectionPool $connectionPool) + public function __construct(private readonly ConnectionPool $connectionPool) { - $this->connectionPool = $connectionPool; } /** diff --git a/packages/ep_products/Classes/ContentFeed/RecordSerializer.php b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php index e725eef..3e5e7e8 100644 --- a/packages/ep_products/Classes/ContentFeed/RecordSerializer.php +++ b/packages/ep_products/Classes/ContentFeed/RecordSerializer.php @@ -18,36 +18,12 @@ use TYPO3\CMS\Core\Database\ConnectionPool; */ class RecordSerializer { - /** - * @var RecordFinder - */ - private $recordFinder; - - /** - * @var UrlResolver - */ - private $urlResolver; - - /** - * @var RteLinkResolver - */ - private $rteLinkResolver; - - /** - * @var ConnectionPool - */ - private $connectionPool; - public function __construct( - RecordFinder $recordFinder, - UrlResolver $urlResolver, - RteLinkResolver $rteLinkResolver, - ConnectionPool $connectionPool + private readonly RecordFinder $recordFinder, + private readonly UrlResolver $urlResolver, + private readonly RteLinkResolver $rteLinkResolver, + private readonly ConnectionPool $connectionPool, ) { - $this->recordFinder = $recordFinder; - $this->urlResolver = $urlResolver; - $this->rteLinkResolver = $rteLinkResolver; - $this->connectionPool = $connectionPool; } /** @@ -62,18 +38,12 @@ class RecordSerializer return []; } - switch ($type) { - case 'product': - return $this->serializeProducts($rows); - case 'hotel': - return $this->serializeHotels($rows); - case 'news': - return $this->serializeNews($rows); - default: - return array_values(array_map(function (array $row) use ($type): array { - return $this->serializeDestination($type, $row); - }, $rows)); - } + return match ($type) { + 'product' => $this->serializeProducts($rows), + 'hotel' => $this->serializeHotels($rows), + 'news' => $this->serializeNews($rows), + default => array_values(array_map(fn(array $row): array => $this->serializeDestination($type, $row), $rows)), + }; } private function serializeProducts(array $rows): array @@ -137,7 +107,7 @@ class RecordSerializer foreach ($rows as $uid => $row) { $code = trim((string) $row['code']); - $codes = array_values(array_unique(array_filter(array_merge([$code], $aliases[$uid] ?? []), 'strlen'))); + $codes = array_values(array_unique(array_filter(array_merge([$code], $aliases[$uid] ?? []), strlen(...)))); $items[] = $this->withLink($row, max((int) $row['tstamp'], $matchcodeTimestamps[$uid] ?? 0)) + [ 'code' => '' === $code ? null : $code, @@ -152,9 +122,7 @@ class RecordSerializer 'countryUid' => (int) $row['country'] ?: null, 'regionUid' => (int) $row['region'] ?: null, 'cityUid' => (int) $row['city'] ?: null, - 'productUids' => array_values(array_filter($products[$uid] ?? [], function (int $productUid) use ($eligibleProducts): bool { - return isset($eligibleProducts[$productUid]); - })), + 'productUids' => array_values(array_filter($products[$uid] ?? [], fn(int $productUid): bool => isset($eligibleProducts[$productUid]))), 'address' => $this->text($row['address']), 'latitude' => $this->text($row['latitude']), 'longitude' => $this->text($row['longitude']), @@ -192,64 +160,61 @@ class RecordSerializer 'headline' => $this->text($row['headline']), ]; - switch ($type) { - case 'region': - return $item + [ - 'season' => $this->text($row['season']), - 'keywords' => $this->text($row['keywords']), - 'countryUid' => (int) $row['country'] ?: null, - 'facts' => [ - 'length' => $this->text($row['length']), - 'height' => $this->text($row['height']), - 'lifts' => $this->text($row['lifts']), - ], - 'webcam' => $this->findWebcamUrl((int) $row['webcam']), - 'text' => [ - 'teaser' => $this->html($row['teaser']), - 'description' => $this->html($row['description']), - 'feature' => $this->text($row['feature']), - 'skiArea' => $this->html($row['ski_area']), - 'skiAreaExtended' => $this->html($row['ski_area_extended']), - 'skiPass' => $this->html($row['ski_pass']), - 'news' => $this->html($row['news']), - ], - ]; - case 'country': - return $item + [ - 'code' => $this->text($row['code']), - 'season' => $this->text($row['season']), - 'keywords' => $this->text($row['keywords']), - 'text' => [ - 'teaser' => $this->html($row['teaser']), - 'description' => $this->html($row['description']), - 'descriptionExtended' => $this->html($row['description_extended']), - ], - ]; - case 'city': - return $item + [ - 'season' => $this->text($row['season']), - 'keywords' => $this->text($row['keywords']), - 'countryUid' => (int) $row['country'] ?: null, - 'regionUid' => (int) $row['region'] ?: null, - 'text' => [ - 'teaser' => $this->html($row['teaser']), - 'description' => $this->html($row['description']), - 'descriptionExtended' => $this->html($row['description_extended']), - 'party' => $this->html($row['party']), - 'leisure' => $this->html($row['leisure']), - ], - ]; - default: // concept - return $item + [ - 'code' => $this->text($row['code']), - 'text' => [ - 'teaser' => $this->html($row['teaser']), - 'description' => $this->html($row['description']), - 'descriptionExtended' => $this->html($row['description_extended']), - 'popupText' => $this->html($row['popup_text']), - ], - ]; - } + return match ($type) { + 'region' => $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'facts' => [ + 'length' => $this->text($row['length']), + 'height' => $this->text($row['height']), + 'lifts' => $this->text($row['lifts']), + ], + 'webcam' => $this->findWebcamUrl((int) $row['webcam']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'feature' => $this->text($row['feature']), + 'skiArea' => $this->html($row['ski_area']), + 'skiAreaExtended' => $this->html($row['ski_area_extended']), + 'skiPass' => $this->html($row['ski_pass']), + 'news' => $this->html($row['news']), + ], + ], + 'country' => $item + [ + 'code' => $this->text($row['code']), + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + ], + ], + 'city' => $item + [ + 'season' => $this->text($row['season']), + 'keywords' => $this->text($row['keywords']), + 'countryUid' => (int) $row['country'] ?: null, + 'regionUid' => (int) $row['region'] ?: null, + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'party' => $this->html($row['party']), + 'leisure' => $this->html($row['leisure']), + ], + ], + // concept + default => $item + [ + 'code' => $this->text($row['code']), + 'text' => [ + 'teaser' => $this->html($row['teaser']), + 'description' => $this->html($row['description']), + 'descriptionExtended' => $this->html($row['description_extended']), + 'popupText' => $this->html($row['popup_text']), + ], + ], + }; } private function serializeNews(array $rows): array @@ -302,8 +267,13 @@ class RecordSerializer * @param int[] $uids * @return array related uids by $keyField, in $sortField order */ - private function findMmUids(string $table, string $keyField, string $valueField, array $uids, string $sortField = 'sorting'): array - { + private function findMmUids( + string $table, + string $keyField, + string $valueField, + array $uids, + string $sortField = 'sorting', + ): array { $queryBuilder = $this->connectionPool->getQueryBuilderForTable($table); $rows = $queryBuilder ->select($keyField, $valueField) @@ -386,7 +356,7 @@ class RecordSerializer */ private function findInfoboxTexts(array $infoboxUids): array { - $infoboxUids = array_values(array_filter(array_map('intval', $infoboxUids))); + $infoboxUids = array_values(array_filter(array_map(intval(...), $infoboxUids))); if ([] === $infoboxUids) { return []; diff --git a/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php index 83dafd8..ba33dd7 100644 --- a/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php +++ b/packages/ep_products/Classes/ContentFeed/RteLinkResolver.php @@ -15,25 +15,13 @@ use TYPO3\CMS\Core\Resource\File; */ class RteLinkResolver { - /** - * @var LinkService - */ - private $linkService; - - /** - * @var UrlResolver - */ - private $urlResolver; - - public function __construct(LinkService $linkService, UrlResolver $urlResolver) + public function __construct(private readonly LinkService $linkService, private readonly UrlResolver $urlResolver) { - $this->linkService = $linkService; - $this->urlResolver = $urlResolver; } public function resolve(string $html): string { - if (false === strpos($html, 't3://')) { + if (!str_contains($html, 't3://')) { return $html; } @@ -52,7 +40,7 @@ class RteLinkResolver { try { $data = $this->linkService->resolve($link); - } catch (\Throwable $e) { + } catch (\Throwable) { return null; } diff --git a/packages/ep_products/Classes/ContentFeed/UrlResolver.php b/packages/ep_products/Classes/ContentFeed/UrlResolver.php index a52c644..5b17d3a 100644 --- a/packages/ep_products/Classes/ContentFeed/UrlResolver.php +++ b/packages/ep_products/Classes/ContentFeed/UrlResolver.php @@ -13,22 +13,12 @@ use TYPO3\CMS\Core\Utility\MathUtility; */ class UrlResolver { - private const DOKTYPE_DEFAULT = 1; - private const DOKTYPE_SHORTCUT = 4; - private const SHORTCUT_MODE_NONE = 0; - private const SHORTCUT_MODE_FIRST_SUBPAGE = 1; - private const SHORTCUT_MODE_PARENT_PAGE = 3; - private const MAX_SHORTCUT_DEPTH = 5; - - /** - * @var SiteFinder - */ - private $siteFinder; - - /** - * @var PageTree - */ - private $pageTree; + private const int DOKTYPE_DEFAULT = 1; + private const int DOKTYPE_SHORTCUT = 4; + private const int SHORTCUT_MODE_NONE = 0; + private const int SHORTCUT_MODE_FIRST_SUBPAGE = 1; + private const int SHORTCUT_MODE_PARENT_PAGE = 3; + private const int MAX_SHORTCUT_DEPTH = 5; /** * @var string[] @@ -45,10 +35,11 @@ class UrlResolver */ private $sitesByPage = []; - public function __construct(SiteFinder $siteFinder, PageTree $pageTree, ContentFeedConfiguration $configuration) - { - $this->siteFinder = $siteFinder; - $this->pageTree = $pageTree; + public function __construct( + private readonly SiteFinder $siteFinder, + private readonly PageTree $pageTree, + ContentFeedConfiguration $configuration, + ) { $this->includedSites = $configuration->getSites(); $this->newsDetailPages = $configuration->getNewsDetailPages(); } @@ -63,7 +54,7 @@ class UrlResolver foreach ($this->includedSites as $identifier) { try { $sites[] = $this->siteFinder->getSiteByIdentifier($identifier); - } catch (SiteNotFoundException $e) { + } catch (SiteNotFoundException) { // A misspelt identifier must not take the whole feed down; it just contributes nothing. } } @@ -80,7 +71,7 @@ class UrlResolver try { $site = $this->siteFinder->getSiteByPageId($pageUid); $this->sitesByPage[$pageUid] = in_array($site->getIdentifier(), $this->includedSites, true) ? $site : null; - } catch (SiteNotFoundException $e) { + } catch (SiteNotFoundException) { $this->sitesByPage[$pageUid] = null; } } @@ -100,7 +91,7 @@ class UrlResolver $language = $site->getLanguageById($languageId); return (string) $site->getRouter()->generateUri($pageUid, ['_language' => $language] + $parameters); - } catch (\Throwable $e) { + } catch (\Throwable) { // Unroutable pages (missing slug, disabled language) are left out rather than failing the feed. return null; } diff --git a/packages/ep_products/Classes/Controller/AjaxCalendarController.php b/packages/ep_products/Classes/Controller/AjaxCalendarController.php index cfb4953..5a670af 100644 --- a/packages/ep_products/Classes/Controller/AjaxCalendarController.php +++ b/packages/ep_products/Classes/Controller/AjaxCalendarController.php @@ -2,6 +2,7 @@ namespace EP\EpProducts\Controller; +use League\Period\Period; use EP\EpProducts\Domain\Model\Hotel; use EP\EpProducts\MyEP\ApiClient as MyEpClient; use EP\EpProducts\MyEP\ApiException; @@ -12,11 +13,8 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; class AjaxCalendarController extends ActionController { - protected MyEpClient $myEpClient; - - public function __construct(MyEpClient $myEpClient) + public function __construct(protected MyEpClient $myEpClient) { - $this->myEpClient = $myEpClient; } public function priceCalendarAction(Hotel $hotel, int $months = 12): ResponseInterface @@ -91,7 +89,7 @@ class AjaxCalendarController extends ActionController if (null !== $lastDataDate) { $dataEnd = $lastDataDate->modify('first day of next month')->setTime(0, 0, 0); if ($dataEnd > $period->getEndDate()) { - $period = new \League\Period\Period($period->getStartDate(), $dataEnd); + $period = new Period($period->getStartDate(), $dataEnd); } } } catch (ApiException $e) { @@ -116,7 +114,7 @@ class AjaxCalendarController extends ActionController public function priceTableAction(Hotel $hotel, ?string $year = null): ResponseInterface { - $year = $year ?? (new \DateTimeImmutable())->format('Y'); + $year ??= new \DateTimeImmutable()->format('Y'); $hotelCode = trim($hotel->getCode() ?? ''); $this->tagPageCacheForHotel($hotelCode); @@ -212,7 +210,7 @@ class AjaxCalendarController extends ActionController /** * @return array> */ - private function buildCalendarMonths(\League\Period\Period $period): array + private function buildCalendarMonths(Period $period): array { $months = []; $current = new \DateTimeImmutable($period->getStartDate()->format('Y-m-01 00:00:00')); diff --git a/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php index 0051060..fa9a7fa 100644 --- a/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php +++ b/packages/ep_products/Classes/Controller/AjaxFilterPanelController.php @@ -19,18 +19,12 @@ class AjaxFilterPanelController extends ActionController */ protected $filterService; - /** - * @var SearchResultService - */ - private $searchResultService; - /** * @param FilterService $filterService */ - public function __construct(FilterService $filterService, SearchResultService $searchResultService) + public function __construct(FilterService $filterService, private SearchResultService $searchResultService) { $this->filterService = $filterService; - $this->searchResultService = $searchResultService; } public function initializeAction(): void diff --git a/packages/ep_products/Classes/Controller/AjaxTableController.php b/packages/ep_products/Classes/Controller/AjaxTableController.php index 9c2641f..6d05a3f 100644 --- a/packages/ep_products/Classes/Controller/AjaxTableController.php +++ b/packages/ep_products/Classes/Controller/AjaxTableController.php @@ -117,7 +117,7 @@ class AjaxTableController extends ActionController if (1 === count($dates)) { $date = reset($dates); - return (new ForwardResponse('pricetable'))->withExtensionName('ep_products')->withArguments([ + return new ForwardResponse('pricetable')->withExtensionName('ep_products')->withArguments([ 'product' => $product, 'hotel' => $hotel, 'date' => $date['dateUid'], @@ -136,9 +136,7 @@ class AjaxTableController extends ActionController 'nonBookableDateUids' => $nonBookableDateUids, ]); - $bookableDates = array_filter($allDates, function (array $date) { - return $date['available'] > 0; - }); + $bookableDates = array_filter($allDates, fn(array $date) => $date['available'] > 0); $includedServices = $this->dateService->groupIncludedServices($bookableDates); $altProductLink = null; @@ -275,12 +273,10 @@ class AjaxTableController extends ActionController public function eventPricetableAction(Product $product): ResponseInterface { $roomTypes = GeneralUtility::trimExplode(',', $this->settings['priceTableRoomTypes'] ?? ''); - $roomTypesPax = array_map(function ($type) { - return (int) $type; - }, $roomTypes); + $roomTypesPax = array_map(fn($type) => (int) $type, $roomTypes); $roomTypesLabels = array_map(function ($type) { $label = (int) $type . 'er'; - if (false !== strpos($type, '+')) { + if (str_contains($type, '+')) { $label .= '+'; } return $label; diff --git a/packages/ep_products/Classes/Controller/CityController.php b/packages/ep_products/Classes/Controller/CityController.php index 29959f8..34cb9e3 100644 --- a/packages/ep_products/Classes/Controller/CityController.php +++ b/packages/ep_products/Classes/Controller/CityController.php @@ -16,19 +16,13 @@ class CityController extends ActionController */ protected $cityRepository; - /** - * @var ProductDataService - */ - private $productDataService; - /** * @param CityRepository $cityRepository * @param ProductDataService $productDataService */ - public function __construct(CityRepository $cityRepository, ProductDataService $productDataService) + public function __construct(CityRepository $cityRepository, private readonly ProductDataService $productDataService) { $this->cityRepository = $cityRepository; - $this->productDataService = $productDataService; } /** diff --git a/packages/ep_products/Classes/Controller/ConceptController.php b/packages/ep_products/Classes/Controller/ConceptController.php index 6566fe8..928894a 100644 --- a/packages/ep_products/Classes/Controller/ConceptController.php +++ b/packages/ep_products/Classes/Controller/ConceptController.php @@ -23,21 +23,18 @@ class ConceptController extends ActionController */ protected $productDataService; - /** - * @var PageRepository - */ - private $pageRepository; - /** * @param ConceptRepository $conceptRepository * @param ProductDataService $productDataService * @param PageRepository $pageRepository */ - public function __construct(ConceptRepository $conceptRepository, ProductDataService $productDataService, PageRepository $pageRepository) - { + public function __construct( + ConceptRepository $conceptRepository, + ProductDataService $productDataService, + private readonly PageRepository $pageRepository, + ) { $this->conceptRepository = $conceptRepository; $this->productDataService = $productDataService; - $this->pageRepository = $pageRepository; } /** diff --git a/packages/ep_products/Classes/Controller/NewsletterController.php b/packages/ep_products/Classes/Controller/NewsletterController.php index 5304c68..42628fb 100644 --- a/packages/ep_products/Classes/Controller/NewsletterController.php +++ b/packages/ep_products/Classes/Controller/NewsletterController.php @@ -10,22 +10,18 @@ use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; class NewsletterController extends ActionController { - private ApiClient $apiClient; - - public function __construct(ApiClient $apiClient) + public function __construct(private readonly ApiClient $apiClient) { - $this->apiClient = $apiClient; } - public function subscriptionFormAction(?NewsletterSubscriptionRequest $subscriptionRequest = null): ResponseInterface - { - if (null === $subscriptionRequest) { - $subscriptionRequest = new NewsletterSubscriptionRequest(); - } + public function subscriptionFormAction( + ?NewsletterSubscriptionRequest $subscriptionRequest = null, + ): ResponseInterface { + $subscriptionRequest ??= new NewsletterSubscriptionRequest(); try { $lists = $this->apiClient->getNewsletterLists(); - } catch (ApiException $e) { + } catch (ApiException) { $lists = []; } diff --git a/packages/ep_products/Classes/Controller/ProductController.php b/packages/ep_products/Classes/Controller/ProductController.php index 2143d37..b6bf26a 100644 --- a/packages/ep_products/Classes/Controller/ProductController.php +++ b/packages/ep_products/Classes/Controller/ProductController.php @@ -50,16 +50,6 @@ class ProductController extends ActionController */ protected $hotelDataService; - /** - * @var InfoboxRepository - */ - private $infoboxRepository; - - /** - * @var PickupOverviewService - */ - private $pickupOverviewService; - /** * @param ProductRepository $productRepository * @param ProductDataService $productDataService @@ -77,8 +67,8 @@ class ProductController extends ActionController HotelDataService $hotelDataService, DateService $dateService, FilterService $filterService, - InfoboxRepository $infoboxRepository, - PickupOverviewService $pickupOverviewService + private readonly InfoboxRepository $infoboxRepository, + private readonly PickupOverviewService $pickupOverviewService ) { $this->productRepository = $productRepository; $this->productDataService = $productDataService; @@ -86,8 +76,6 @@ class ProductController extends ActionController $this->hotelDataService = $hotelDataService; $this->dateService = $dateService; $this->filterService = $filterService; - $this->infoboxRepository = $infoboxRepository; - $this->pickupOverviewService = $pickupOverviewService; } /** @@ -131,7 +119,7 @@ class ProductController extends ActionController $arguments = [ 'product' => $product, ]; - return (new ForwardResponse('hotelList'))->withArguments($arguments); + return new ForwardResponse('hotelList')->withArguments($arguments); } // Use first hotel which should be the only one at this point if ($product->getHotels()->count() > 0) { diff --git a/packages/ep_products/Classes/Controller/RegionController.php b/packages/ep_products/Classes/Controller/RegionController.php index ea742fc..01066be 100644 --- a/packages/ep_products/Classes/Controller/RegionController.php +++ b/packages/ep_products/Classes/Controller/RegionController.php @@ -42,9 +42,7 @@ class RegionController extends ActionController $region = $this->regionRepository->findByIdentifier($regionUid); } $additionalRegionUids = GeneralUtility::trimExplode(',', ($this->settings['additionalRegionUids'] ?? ''), true); - $additionalRegionUids = array_map(function ($uid) { - return (int)$uid; - }, $additionalRegionUids); + $additionalRegionUids = array_map(fn($uid) => (int)$uid, $additionalRegionUids); $teaserRegionUids = array_merge([$region->getUid()], $additionalRegionUids); $filterSettings = [ 'regionUids' => $teaserRegionUids, diff --git a/packages/ep_products/Classes/Controller/SearchController.php b/packages/ep_products/Classes/Controller/SearchController.php index 064d9e8..5e8922a 100644 --- a/packages/ep_products/Classes/Controller/SearchController.php +++ b/packages/ep_products/Classes/Controller/SearchController.php @@ -27,18 +27,12 @@ class SearchController extends ActionController */ protected $filterService; - /** - * @var SearchResultService - */ - private $searchResultService; - /** * @param FilterService $filterService */ - public function __construct(FilterService $filterService, SearchResultService $searchResultService) + public function __construct(FilterService $filterService, private SearchResultService $searchResultService) { $this->filterService = $filterService; - $this->searchResultService = $searchResultService; } public function initializeSearchresultAction() @@ -57,7 +51,7 @@ class SearchController extends ActionController // The fields of the search bar, which sends them here by GET $searchbarParameters = array_filter( array_intersect_key($this->request->getQueryParams(), array_flip(self::SEARCHBAR_PARAMETERS)), - 'is_string' + is_string(...) ); // Anything but a real Y-m-d date is dropped: the URL is easy to edit, and // DateUtility::formatDateRange() throws on what DateTime cannot read diff --git a/packages/ep_products/Classes/Controller/TravelinfoController.php b/packages/ep_products/Classes/Controller/TravelinfoController.php index 4d640fe..9e04008 100644 --- a/packages/ep_products/Classes/Controller/TravelinfoController.php +++ b/packages/ep_products/Classes/Controller/TravelinfoController.php @@ -14,22 +14,12 @@ use TYPO3\CMS\Frontend\Controller\ErrorController; class TravelinfoController extends ActionController { - private const SELECTION_ID_BOARD = 71; + private const int SELECTION_ID_BOARD = 71; - /** - * @var ApiClient - */ - private $apiClient; - - /** - * @var TravelinfoRepository - */ - private $travelinfoRepository; - - public function __construct(ApiClient $apiClient, TravelinfoRepository $travelinfoRepository) - { - $this->apiClient = $apiClient; - $this->travelinfoRepository = $travelinfoRepository; + public function __construct( + private readonly ApiClient $apiClient, + private readonly TravelinfoRepository $travelinfoRepository, + ) { } public function travelCodeAction(string $travelCode): ResponseInterface @@ -86,14 +76,12 @@ class TravelinfoController extends ActionController try { $pickupsPlanning = $this->apiClient->getPickupsPlanning($travelCode); - } catch (ApiException $e) { + } catch (ApiException) { $pickupsPlanning = []; } // extract courses from additional services - $courses = array_filter($travelData['additionalServices'] ?? [], function ($service) { - return 'KUR' === $service['subType']; - }); + $courses = array_filter($travelData['additionalServices'] ?? [], fn($service) => 'KUR' === $service['subType']); // extract board services from included services $includedServicesBoard = $travelData['selectionGroups'][self::SELECTION_ID_BOARD] ?? []; @@ -101,9 +89,7 @@ class TravelinfoController extends ActionController // extract bus departure time if applicable $departureDayTime = null; - $service = array_filter($travelData['transportationServices'] ?? [], function ($service) { - return 'BUS' === $service['subType'] && 'RUECK' === $service['direction']; - }); + $service = array_filter($travelData['transportationServices'] ?? [], fn($service) => 'BUS' === $service['subType'] && 'RUECK' === $service['direction']); if ($serviceDetail = reset($service)) { $departureDayTime = $serviceDetail['dayTime']; } diff --git a/packages/ep_products/Classes/Domain/Model/Country.php b/packages/ep_products/Classes/Domain/Model/Country.php index 76a05b1..bc1de4e 100644 --- a/packages/ep_products/Classes/Domain/Model/Country.php +++ b/packages/ep_products/Classes/Domain/Model/Country.php @@ -15,6 +15,7 @@ class Country extends AbstractDestination implements TeaserInterface /** * @var string */ + #[\Override] protected $descriptionExtended = ''; /** diff --git a/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php index efd20be..3ea9322 100644 --- a/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php +++ b/packages/ep_products/Classes/Domain/Model/Dto/NewsletterSubscriptionRequest.php @@ -98,9 +98,7 @@ class NewsletterSubscriptionRequest implements JsonSerializable 'firstName' => $this->firstName, 'lastName' => $this->lastName, 'email' => $this->email, - 'listIds' => array_map(function($value) { - return (int) $value; - }, $this->listIds), + 'listIds' => array_map(fn($value) => (int) $value, $this->listIds), ]; } } diff --git a/packages/ep_products/Classes/Domain/Model/SearchResult.php b/packages/ep_products/Classes/Domain/Model/SearchResult.php index 21aa5c7..89c0dd1 100644 --- a/packages/ep_products/Classes/Domain/Model/SearchResult.php +++ b/packages/ep_products/Classes/Domain/Model/SearchResult.php @@ -5,11 +5,6 @@ namespace EP\EpProducts\Domain\Model; class SearchResult { - /** - * @var int - */ - protected $total = 0; - /** * @var array */ @@ -19,10 +14,9 @@ class SearchResult * @param array $data * @param int $total */ - public function __construct(array $data = [], $total = 0) + public function __construct(array $data = [], protected $total = 0) { $this->data = $data; - $this->total = $total; } /** diff --git a/packages/ep_products/Classes/Domain/Repository/CityRepository.php b/packages/ep_products/Classes/Domain/Repository/CityRepository.php index cc40123..7447eb3 100644 --- a/packages/ep_products/Classes/Domain/Repository/CityRepository.php +++ b/packages/ep_products/Classes/Domain/Repository/CityRepository.php @@ -6,11 +6,13 @@ use EP\EpProducts\Domain\Model\AbstractDestination; class CityRepository extends AbstractRepository { - public function getTeasersByCountryOrRegionAndSeason(int $regionUid = 0, int $countryUid = 0, string $season = 'w', int $storagePid = 0) - { - if ($season === null) { - $season = AbstractDestination::SEASON_WINTER; - } + public function getTeasersByCountryOrRegionAndSeason( + int $regionUid = 0, + int $countryUid = 0, + string $season = 'w', + int $storagePid = 0, + ) { + $season ??= AbstractDestination::SEASON_WINTER; $query = $this->createQuery(); $constraints = [ $query->equals('season', $season), diff --git a/packages/ep_products/Classes/Domain/Repository/RegionRepository.php b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php index eece9f5..1e36d00 100644 --- a/packages/ep_products/Classes/Domain/Repository/RegionRepository.php +++ b/packages/ep_products/Classes/Domain/Repository/RegionRepository.php @@ -8,9 +8,7 @@ class RegionRepository extends AbstractRepository { public function getTeasersByCountryAndSeason(int $countryUid, string $season = 'w', int $storagePid = 0) { - if ($season === null) { - $season = AbstractDestination::SEASON_WINTER; - } + $season ??= AbstractDestination::SEASON_WINTER; $query = $this->createQuery(); $constraints = [ $query->equals('country', $countryUid), diff --git a/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php index 3d0f570..d8cbf35 100644 --- a/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php +++ b/packages/ep_products/Classes/Domain/Repository/TeammemberRepository.php @@ -10,6 +10,7 @@ class TeammemberRepository extends AbstractRepository /** * @var array */ + #[\Override] protected $defaultOrderings = [ 'sorting' => 'ASC', ]; diff --git a/packages/ep_products/Classes/EpTeam/ApiClient.php b/packages/ep_products/Classes/EpTeam/ApiClient.php index b348592..37bab05 100644 --- a/packages/ep_products/Classes/EpTeam/ApiClient.php +++ b/packages/ep_products/Classes/EpTeam/ApiClient.php @@ -25,15 +25,15 @@ class ApiClient implements LoggerAwareInterface /** * Applied when extension configuration does not override them. Seconds. */ - private const DEFAULT_TIMEOUT = 5; - private const DEFAULT_MAX_DURATION = 15; + private const int DEFAULT_TIMEOUT = 5; + private const int DEFAULT_MAX_DURATION = 15; /** * Status codes the endpoint answers a stored submission with. A conflict is included for the * dedupe the team application may add later - it is not returned today - because a * double-clicked submit button is not something to report. */ - private const ACCEPTED_STATUS_CODES = [ + private const array ACCEPTED_STATUS_CODES = [ Response::HTTP_OK, Response::HTTP_CREATED, Response::HTTP_ACCEPTED, @@ -46,7 +46,7 @@ class ApiClient implements LoggerAwareInterface * offending field in its body, which is the only way to tell from a log why a submission * was refused. */ - private const ERROR_BODY_EXCERPT_LENGTH = 500; + private const int ERROR_BODY_EXCERPT_LENGTH = 500; /** * Shared, like the MyE&P client: Symfony's CurlHttpClient keeps its connection pool and TLS @@ -126,7 +126,7 @@ class ApiClient implements LoggerAwareInterface { try { $body = trim($response->getContent(false)); - } catch (TransportExceptionInterface $e) { + } catch (TransportExceptionInterface) { return ''; } @@ -151,7 +151,7 @@ class ApiClient implements LoggerAwareInterface { try { $data = $response->toArray(false); - } catch (\Throwable $e) { + } catch (\Throwable) { // A body that is absent (204) or not JSON costs nothing here - the submission is // stored either way, which the asserted status has already established. return; @@ -185,7 +185,7 @@ class ApiClient implements LoggerAwareInterface if ('' === $apiKey) { throw new ApiException('EP Team API key is not configured.'); } - if (0 !== strpos($baseUrl, 'https://')) { + if (!str_starts_with($baseUrl, 'https://')) { // The application answers an http request with a 301 to the https URL, and this // client does not follow redirects, so a plain http base URL fails every submission. $this->logWarning(sprintf('EP Team API base URL is not https (%s); submissions will fail on the redirect.', $baseUrl)); @@ -220,10 +220,8 @@ class ApiClient implements LoggerAwareInterface private function getConfiguration(): array { - if (null === $this->configuration) { - $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) - ->get('ep_products'); - } + $this->configuration ??= GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); return $this->configuration; } diff --git a/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php index 3d87c03..d35161d 100644 --- a/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php +++ b/packages/ep_products/Classes/Http/ApiKeyAuthenticator.php @@ -14,14 +14,8 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; */ class ApiKeyAuthenticator { - /** - * @var ExtensionConfiguration - */ - private $extensionConfiguration; - - public function __construct(ExtensionConfiguration $extensionConfiguration) + public function __construct(private readonly ExtensionConfiguration $extensionConfiguration) { - $this->extensionConfiguration = $extensionConfiguration; } public function isAuthorized(ServerRequestInterface $request): bool diff --git a/packages/ep_products/Classes/Middleware/ApiMiddleware.php b/packages/ep_products/Classes/Middleware/ApiMiddleware.php index 41ede65..635d9cf 100644 --- a/packages/ep_products/Classes/Middleware/ApiMiddleware.php +++ b/packages/ep_products/Classes/Middleware/ApiMiddleware.php @@ -19,57 +19,15 @@ use TYPO3\CMS\Extbase\Service\ImageService; class ApiMiddleware implements MiddlewareInterface { - /** - * @var MyEpImageService - */ - private $hotelImageService; - - /** - * @var MyEpImageService - */ - private $productImageService; - - /** - * @var MyEpImageService - */ - private $regionImageService; - - /** - * @var FileRepository - */ - private $fileRepository; - - /** - * @var ImageService - */ - private $imageService; - - /** - * @var ContentFeedApi - */ - private $contentFeedApi; - - /** - * @var ApiKeyAuthenticator - */ - private $authenticator; - public function __construct( - MyEpImageService $hotelImageService, - MyEpImageService $productImageService, - MyEpImageService $regionImageService, - FileRepository $fileRepository, - ImageService $imageService, - ContentFeedApi $contentFeedApi, - ApiKeyAuthenticator $authenticator + private readonly MyEpImageService $hotelImageService, + private readonly MyEpImageService $productImageService, + private readonly MyEpImageService $regionImageService, + private readonly FileRepository $fileRepository, + private readonly ImageService $imageService, + private readonly ContentFeedApi $contentFeedApi, + private readonly ApiKeyAuthenticator $authenticator, ) { - $this->hotelImageService = $hotelImageService; - $this->productImageService = $productImageService; - $this->regionImageService = $regionImageService; - $this->fileRepository = $fileRepository; - $this->imageService = $imageService; - $this->contentFeedApi = $contentFeedApi; - $this->authenticator = $authenticator; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -84,7 +42,7 @@ class ApiMiddleware implements MiddlewareInterface return $this->handleProductRequest($request); } - if (0 === strpos($path, ContentFeedApi::PATH_PREFIX)) { + if (str_starts_with($path, ContentFeedApi::PATH_PREFIX)) { return $this->contentFeedApi->handle($request); } @@ -309,12 +267,10 @@ class ApiMiddleware implements MiddlewareInterface private function serializeFileReferences(array $files): array { - return array_map(function ($file) { - return [ - 'url' => $this->imageService->getImageUri($file, true), - 'alt' => $file->getProperty('alternative'), - 'title' => $file->getProperty('title'), - ]; - }, $files); + return array_map(fn($file) => [ + 'url' => $this->imageService->getImageUri($file, true), + 'alt' => $file->getProperty('alternative'), + 'title' => $file->getProperty('title'), + ], $files); } } diff --git a/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php index 4f3409a..aabf632 100644 --- a/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php +++ b/packages/ep_products/Classes/Middleware/ContentPageTypeGuardMiddleware.php @@ -24,20 +24,10 @@ use TYPO3\CMS\Frontend\Controller\ErrorController; */ class ContentPageTypeGuardMiddleware implements MiddlewareInterface { - /** - * @var ApiKeyAuthenticator - */ - private $authenticator; - - /** - * @var ContentFeedConfiguration - */ - private $configuration; - - public function __construct(ApiKeyAuthenticator $authenticator, ContentFeedConfiguration $configuration) - { - $this->authenticator = $authenticator; - $this->configuration = $configuration; + public function __construct( + private readonly ApiKeyAuthenticator $authenticator, + private readonly ContentFeedConfiguration $configuration, + ) { } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface diff --git a/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php index 816d8fd..51aa252 100644 --- a/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php +++ b/packages/ep_products/Classes/Middleware/StaticSearchParamsMiddleware.php @@ -27,7 +27,7 @@ class StaticSearchParamsMiddleware implements MiddlewareInterface $params = $request->getAttribute('normalizedParams'); $uri = $params->getRequestUri(); - if (strpos($uri, self::PREFIX_PATH_SEGMENT) === false) { + if (!str_contains($uri, self::PREFIX_PATH_SEGMENT)) { return $handler->handle($request); } diff --git a/packages/ep_products/Classes/MyEP/ApiClient.php b/packages/ep_products/Classes/MyEP/ApiClient.php index 0277e85..59d84ef 100644 --- a/packages/ep_products/Classes/MyEP/ApiClient.php +++ b/packages/ep_products/Classes/MyEP/ApiClient.php @@ -28,31 +28,31 @@ class ApiClient implements LoggerAwareInterface /** * Applied when extension configuration does not override them. Seconds. */ - private const DEFAULT_TIMEOUT = 5; - private const DEFAULT_MAX_DURATION = 15; + private const int DEFAULT_TIMEOUT = 5; + private const int DEFAULT_MAX_DURATION = 15; /** * Cache identifier for the shared OAuth access token. The token is valid for the whole * installation, so caching it here saves one round trip per request that would otherwise * have to re-authenticate before it can fetch anything. */ - private const ACCESS_TOKEN_CACHE_IDENTIFIER = 'myep_api_access_token'; + private const string ACCESS_TOKEN_CACHE_IDENTIFIER = 'myep_api_access_token'; /** * Safety margin in seconds. A token that is about to expire is treated as expired so it * cannot lapse between the cache read and the API call that uses it. */ - private const ACCESS_TOKEN_EXPIRY_MARGIN = 60; + private const int ACCESS_TOKEN_EXPIRY_MARGIN = 60; /** * Fallback lifetime for tokens that do not report an expiry. */ - private const ACCESS_TOKEN_DEFAULT_TTL = 300; + private const int ACCESS_TOKEN_DEFAULT_TTL = 300; /** * Status codes that mean the token was rejected rather than the request being wrong. */ - private const UNAUTHORIZED_STATUS_CODES = [Response::HTTP_UNAUTHORIZED, Response::HTTP_FORBIDDEN]; + private const array UNAUTHORIZED_STATUS_CODES = [Response::HTTP_UNAUTHORIZED, Response::HTTP_FORBIDDEN]; private static ?AccessToken $accessToken = null; @@ -192,8 +192,11 @@ class ApiClient implements LoggerAwareInterface * * @throws ApiException */ - public function getContingentCalendar(string $hotelCode, \DateTimeInterface $dateFrom, \DateTimeInterface $dateTo): array - { + public function getContingentCalendar( + string $hotelCode, + \DateTimeInterface $dateFrom, + \DateTimeInterface $dateTo, + ): array { return $this->requestJson('GET', 'contingents/calendar', [ 'query' => [ 'hotelCode' => $hotelCode, @@ -219,8 +222,12 @@ class ApiClient implements LoggerAwareInterface /** * @throws ApiException */ - private function requestJson(string $method, string $uri, array $options = [], array $expectedStatusCodes = [Response::HTTP_OK]): array - { + private function requestJson( + string $method, + string $uri, + array $options = [], + array $expectedStatusCodes = [Response::HTTP_OK], + ): array { $response = $this->request($method, $uri, $options); $this->assertStatusCode($response, $expectedStatusCodes, $method, $uri); @@ -271,8 +278,12 @@ class ApiClient implements LoggerAwareInterface * * @throws ApiException */ - private function assertStatusCode(ResponseInterface $response, array $expectedStatusCodes, string $method, string $uri): void - { + private function assertStatusCode( + ResponseInterface $response, + array $expectedStatusCodes, + string $method, + string $uri, + ): void { $statusCode = $this->readStatusCode($response, $method, $uri); if (true === in_array($statusCode, $expectedStatusCodes, true)) { @@ -312,7 +323,7 @@ class ApiClient implements LoggerAwareInterface { try { $response->getContent(false); - } catch (TransportExceptionInterface $e) { + } catch (TransportExceptionInterface) { // The status has already been asserted, so a failure here costs nothing but the // connection reuse this method exists for. } @@ -375,19 +386,15 @@ class ApiClient implements LoggerAwareInterface */ private function getCache(): FrontendInterface { - if (null === $this->cache) { - $this->cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('ep_products_cache'); - } + $this->cache ??= GeneralUtility::makeInstance(CacheManager::class)->getCache('ep_products_cache'); return $this->cache; } private function getConfiguration(): array { - if (null === $this->configuration) { - $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class) - ->get('ep_products'); - } + $this->configuration ??= GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('ep_products'); return $this->configuration; } diff --git a/packages/ep_products/Classes/Service/ApiImportSource.php b/packages/ep_products/Classes/Service/ApiImportSource.php index e918751..e65c378 100644 --- a/packages/ep_products/Classes/Service/ApiImportSource.php +++ b/packages/ep_products/Classes/Service/ApiImportSource.php @@ -27,25 +27,19 @@ final class ApiImportSource implements ProductImportSource, SingletonInterface, /** * The whole export is one page at current volumes; the walk does not rely on it. */ - private const PER_PAGE = 500; + private const int PER_PAGE = 500; /** * How often the walk restarts when MyE&P replaces its copy of the export * mid-walk before giving up until the next run. */ - private const MAX_WALKS = 3; + private const int MAX_WALKS = 3; - private const STAGING_SUBDIR = 'ep_products_import'; - private const BASELINE_FILE = 'api-imported.info'; + private const string STAGING_SUBDIR = 'ep_products_import'; + private const string BASELINE_FILE = 'api-imported.info'; - /** - * @var ApiClient|null - */ - private $apiClient; - - public function __construct(?ApiClient $apiClient = null) + public function __construct(private ?ApiClient $apiClient = null) { - $this->apiClient = $apiClient; } /** @@ -202,16 +196,14 @@ final class ApiImportSource implements ProductImportSource, SingletonInterface, try { return new \DateTimeImmutable($value); - } catch (\Exception $e) { + } catch (\Exception) { return null; } } private function getApiClient(): ApiClient { - if ($this->apiClient === null) { - $this->apiClient = GeneralUtility::makeInstance(ApiClient::class); - } + $this->apiClient ??= GeneralUtility::makeInstance(ApiClient::class); return $this->apiClient; } diff --git a/packages/ep_products/Classes/Service/DateImportService.php b/packages/ep_products/Classes/Service/DateImportService.php index 69c5058..b87e92b 100644 --- a/packages/ep_products/Classes/Service/DateImportService.php +++ b/packages/ep_products/Classes/Service/DateImportService.php @@ -157,9 +157,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface $products = []; foreach ($rows as $row) { $code = $row['product']['code']; - if (!isset($products[$code])) { - $products[$code] = ['busProId' => (int) $row['product']['busProId'], 'dates' => []]; - } + $products[$code] ??= ['busProId' => (int) $row['product']['busProId'], 'dates' => []]; $products[$code]['dates'][(int) $row['date']['busProId']][] = $row; } @@ -401,9 +399,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface $servicesTransportationOptional = []; if (array_key_exists('BUS', $servicesTransportation)) { - $servicesTransportationOptional = array_filter($servicesTransportation['BUS'], function ($service) { - return $service['price'] !== 0; - }); + $servicesTransportationOptional = array_filter($servicesTransportation['BUS'], fn($service) => $service['price'] !== 0); } if (array_key_exists('BAH', $servicesTransportation)) { $servicesTransportationOptional = array_merge( @@ -433,9 +429,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface foreach ($combinedServices as $key => $list) { - uasort($list, function ($a, $b) { - return strcmp($a['label'], $b['label']); - }); + uasort($list, fn($a, $b) => strcmp($a['label'], $b['label'])); $combinedServices[$key] = $list; } $date['services_general'] = serialize($combinedServices); @@ -488,7 +482,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface $code = $row['hotel']['code']; $busProId = (int) $row['hotel']['busProId']; - if (!array_key_exists($code, $this->hotelMappings)) { + if (!array_key_exists((string) $code, $this->hotelMappings)) { $this->logger->warning('hotel not found', ['code' => $code]); return false; @@ -503,9 +497,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface $this->addServices($generalServices, $services); $this->addServices($hotelServices, $services); foreach ($services as $key => $list) { - uasort($list, function ($a, $b) { - return strcmp($a['label'], $b['label']); - }); + uasort($list, fn($a, $b) => strcmp($a['label'], $b['label'])); $services[$key] = $list; } @@ -696,7 +688,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface $label = static::LABEL_TRAIN; } - if (!array_key_exists($type, $data)) { + if (!array_key_exists((string) $type, $data)) { $data[$type] = []; } @@ -841,7 +833,7 @@ class DateImportService implements SingletonInterface, LoggerAwareInterface return [$parts[3] . '.' . $parts[2] . '.' . $parts[1], $parts[4] . ':' . $parts[5]]; } - $pickupDateTime = (new \DateTimeImmutable($datetime))->setTimezone(new \DateTimeZone('Europe/Berlin')); + $pickupDateTime = new \DateTimeImmutable($datetime)->setTimezone(new \DateTimeZone('Europe/Berlin')); return [$pickupDateTime->format('d.m.Y'), $pickupDateTime->format('H:i')]; } diff --git a/packages/ep_products/Classes/Service/DateService.php b/packages/ep_products/Classes/Service/DateService.php index 9d9d993..597a913 100644 --- a/packages/ep_products/Classes/Service/DateService.php +++ b/packages/ep_products/Classes/Service/DateService.php @@ -97,9 +97,7 @@ class DateService implements SingletonInterface $myEpUrl = $resolvedOptions['myEpUrl']; $paCode = $resolvedOptions['paCode'] ?? null; - if ($filterSettings === null) { - $filterSettings = $this->filterService->getNormalizedFilterSettings(); - } + $filterSettings ??= $this->filterService->getNormalizedFilterSettings(); $nonBookableDateUids = GeneralUtility::trimExplode(',', $product->getNonBookableDates(), true); @@ -137,9 +135,7 @@ class DateService implements SingletonInterface $hotel = $resolvedOptions['hotel']; $filterSettings = $resolvedOptions['filterSettings'] ?? null; - if ($filterSettings === null) { - $filterSettings = $this->filterService->getNormalizedFilterSettings(); - } + $filterSettings ??= $this->filterService->getNormalizedFilterSettings(); return $this->productRepository->getAvailableDates($product, $hotel, $filterSettings); } @@ -231,16 +227,14 @@ class DateService implements SingletonInterface $processed = []; foreach ($servicesFlat as $key => $services) { - $servicesWithCosts = array_filter($services, function ($service) { - return '0,00' !== $service['price']; - }); + $servicesWithCosts = array_filter($services, fn($service) => '0,00' !== $service['price']); if (0 === count($servicesWithCosts)) { continue; } if (array_key_exists($key, $this->serviceSectionsMapping)) { $key = $this->serviceSectionsMapping[$key]; } - if (!array_key_exists($key, $processed)) { + if (!array_key_exists((string) $key, $processed)) { $processed[$key] = [ 'label' => $this->serviceSections[$key] ?? null, 'services' => [], @@ -345,7 +339,7 @@ class DateService implements SingletonInterface } $hotelUid = $row['hotelUid']; - if (!array_key_exists($hotelUid, $priceTable[$categoryUid])) { + if (!array_key_exists((string) $hotelUid, $priceTable[$categoryUid])) { $priceTable[$categoryUid][$hotelUid] = [ 'category' => $categoryUid, @@ -375,7 +369,7 @@ class DateService implements SingletonInterface // Add new entry or replace possible existing entry that is not // bookable with current record in case of matching category // and pax - if (!array_key_exists($roomPax, $entry)) { + if (!array_key_exists((string) $roomPax, $entry)) { $entry[$roomPax] = [ 'price' => $row['roomPrice'], 'available' => $row['available'] && $row['roomPrice'], @@ -398,12 +392,10 @@ class DateService implements SingletonInterface { // The rooms are keyed by pax, so this compares the 4-person prices; hotels without // one compare as '' (what PHP 7.4 read from the missing offset) - uasort($rows, function ($a, $b) { - return strcmp( - $a['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] ?? '', - $b['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] ?? '' - ); - }); + uasort($rows, fn($a, $b) => strcmp( + $a['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] ?? '', + $b['rooms'][Hotel::CATEGORY_SUPER_DELUXE]['price'] ?? '' + )); $priceTable[$category] = $rows; } @@ -412,13 +404,7 @@ class DateService implements SingletonInterface public function hasIncludedServices(array $tableData): bool { - foreach ($tableData as $row) { - if (($row['busIncluded'] ?? null) || ($row['skiPassIncluded'] ?? null)) { - return true; - } - } - - return false; + return array_any($tableData, fn($row) => ($row['busIncluded'] ?? null) || ($row['skiPassIncluded'] ?? null)); } /** diff --git a/packages/ep_products/Classes/Service/FilterService.php b/packages/ep_products/Classes/Service/FilterService.php index 1b44591..9c0ec93 100644 --- a/packages/ep_products/Classes/Service/FilterService.php +++ b/packages/ep_products/Classes/Service/FilterService.php @@ -39,21 +39,17 @@ class FilterService implements SingletonInterface 'bus' => null, ]); foreach (['hotelUid', 'productUid', 'nights', 'pax'] as $key) { - $optionsResolver->setNormalizer($key, function (Options $options, $value) { - return null !== $value ? (int)$value : null; - }); + $optionsResolver->setNormalizer($key, fn(Options $options, $value) => null !== $value ? (int)$value : null); } foreach (['productsPid', 'priceRanges', 'countryUids', 'regionUids', 'cityUids', 'conceptUids', 'boardTypes', 'hotelTypes', 'roomTypes'] as $key) { $optionsResolver->setNormalizer($key, function (Options $options, $value) { if (empty($value)) { return []; } - return array_map('intval', (array) $value); + return array_map(intval(...), (array) $value); }); } - $optionsResolver->setNormalizer('bus', function (Options $options, $value) { - return null !== $value ? (bool)$value : null; - }); + $optionsResolver->setNormalizer('bus', fn(Options $options, $value) => null !== $value ? (bool)$value : null); $filterSettings = $optionsResolver->resolve($filterSettings); @@ -250,9 +246,7 @@ class FilterService implements SingletonInterface } foreach ($destinations as $key => $value) { - usort($value['regions'], function ($a, $b) { - return strcmp($a['label'], $b['label']); - }); + usort($value['regions'], fn($a, $b) => strcmp($a['label'], $b['label'])); $destinations[$key] = $value; } sort($destinations); @@ -260,9 +254,7 @@ class FilterService implements SingletonInterface sort($roomTypes); ksort($priceRanges); - usort($boardTypes, function ($a, $b) { - return strcmp($a['label'], $b['label']); - }); + usort($boardTypes, fn($a, $b) => strcmp($a['label'], $b['label'])); $filterOptions = new FilterOptions(); $filterOptions->setPriceRanges($priceRanges); diff --git a/packages/ep_products/Classes/Service/GroupsSwissService.php b/packages/ep_products/Classes/Service/GroupsSwissService.php index 0c59d26..357a70e 100644 --- a/packages/ep_products/Classes/Service/GroupsSwissService.php +++ b/packages/ep_products/Classes/Service/GroupsSwissService.php @@ -22,11 +22,11 @@ class GroupsSwissService implements LoggerAwareInterface { use LoggerAwareTrait; - private const API_URL = 'https://www.groups.swiss/api/1.0/json.php'; + private const string API_URL = 'https://www.groups.swiss/api/1.0/json.php'; - private const STATE_FREE = 0; - private const STATE_ON_REQUEST = 1; - private const STATE_BLOCKED = 2; + private const int STATE_FREE = 0; + private const int STATE_ON_REQUEST = 1; + private const int STATE_BLOCKED = 2; private HttpClientInterface $httpClient; diff --git a/packages/ep_products/Classes/Service/HotelDataService.php b/packages/ep_products/Classes/Service/HotelDataService.php index e2df5f6..978f169 100644 --- a/packages/ep_products/Classes/Service/HotelDataService.php +++ b/packages/ep_products/Classes/Service/HotelDataService.php @@ -116,7 +116,7 @@ class HotelDataService implements SingletonInterface $hotelCategory = Hotel::$categoryLabels[$row['hotelCategory']] ?? null; - if (!array_key_exists($hotelUid, $teasers)) { + if (!array_key_exists((string) $hotelUid, $teasers)) { $teasers[$hotelUid] = [ 'minDateStart' => null, 'maxDateEnd' => null, diff --git a/packages/ep_products/Classes/Service/HotelImageService.php b/packages/ep_products/Classes/Service/HotelImageService.php index ce07518..9231769 100644 --- a/packages/ep_products/Classes/Service/HotelImageService.php +++ b/packages/ep_products/Classes/Service/HotelImageService.php @@ -6,6 +6,7 @@ class HotelImageService extends AbstractImageService /** * @var string */ + #[\Override] protected $table = 'tx_epproducts_domain_model_hotel'; /** diff --git a/packages/ep_products/Classes/Service/PickupOverviewService.php b/packages/ep_products/Classes/Service/PickupOverviewService.php index f05d007..38f639f 100644 --- a/packages/ep_products/Classes/Service/PickupOverviewService.php +++ b/packages/ep_products/Classes/Service/PickupOverviewService.php @@ -19,14 +19,8 @@ use EP\EpProducts\Domain\Repository\DateRepository; */ class PickupOverviewService { - /** - * @var DateRepository - */ - private $dateRepository; - - public function __construct(DateRepository $dateRepository) + public function __construct(private readonly DateRepository $dateRepository) { - $this->dateRepository = $dateRepository; } /** diff --git a/packages/ep_products/Classes/Service/ProductImageService.php b/packages/ep_products/Classes/Service/ProductImageService.php index 9915c37..77fa572 100644 --- a/packages/ep_products/Classes/Service/ProductImageService.php +++ b/packages/ep_products/Classes/Service/ProductImageService.php @@ -6,6 +6,7 @@ class ProductImageService extends AbstractImageService /** * @var string */ + #[\Override] protected $table = 'tx_epproducts_domain_model_product'; /** diff --git a/packages/ep_products/Classes/Service/ProductImport.php b/packages/ep_products/Classes/Service/ProductImport.php index 2cdc0d4..23a64e5 100644 --- a/packages/ep_products/Classes/Service/ProductImport.php +++ b/packages/ep_products/Classes/Service/ProductImport.php @@ -7,33 +7,18 @@ namespace EP\EpProducts\Service; * {@see DateImportService::import()}, the source's own timestamp, and whatever * the source needs to find it again in {@see ProductImportSource::markImported()}. */ -final class ProductImport +final readonly class ProductImport { - /** - * @var iterable - */ - private $rows; - - /** - * @var \DateTimeImmutable|null - */ - private $sourceGeneratedAt; - - /** - * @var string|null - */ - private $location; - /** * @param iterable $rows * @param \DateTimeImmutable|null $sourceGeneratedAt * @param string|null $location local directory of an XML export, null otherwise */ - public function __construct(iterable $rows, ?\DateTimeImmutable $sourceGeneratedAt, ?string $location = null) - { - $this->rows = $rows; - $this->sourceGeneratedAt = $sourceGeneratedAt; - $this->location = $location; + public function __construct( + private iterable $rows, + private ?\DateTimeImmutable $sourceGeneratedAt, + private ?string $location = null, + ) { } /** diff --git a/packages/ep_products/Classes/Service/RoomTypeResolver.php b/packages/ep_products/Classes/Service/RoomTypeResolver.php index e04b6ad..eeeb41f 100644 --- a/packages/ep_products/Classes/Service/RoomTypeResolver.php +++ b/packages/ep_products/Classes/Service/RoomTypeResolver.php @@ -93,12 +93,6 @@ class RoomTypeResolver */ protected function matchesAny(string $label, array $patterns): bool { - foreach ($patterns as $pattern) { - if (mb_strpos(mb_strtolower($label), mb_strtolower($pattern)) !== false) { - return true; - } - } - - return false; + return array_any($patterns, fn($pattern) => str_contains(mb_strtolower($label), mb_strtolower($pattern))); } } diff --git a/packages/ep_products/Classes/Service/SearchResultService.php b/packages/ep_products/Classes/Service/SearchResultService.php index 3731a8d..8a829b7 100644 --- a/packages/ep_products/Classes/Service/SearchResultService.php +++ b/packages/ep_products/Classes/Service/SearchResultService.php @@ -249,9 +249,7 @@ class SearchResultService implements SingletonInterface continue; } - uasort($section['items'], function ($a, $b) { - return strcmp((string)$a['minPrice'], (string)$b['minPrice']); - }); + uasort($section['items'], fn($a, $b) => strcmp((string)$a['minPrice'], (string)$b['minPrice'])); } // Turn result data into simple array to ensure order in js diff --git a/packages/ep_products/Classes/Service/SnowreportImportService.php b/packages/ep_products/Classes/Service/SnowreportImportService.php index 78737ef..856c017 100644 --- a/packages/ep_products/Classes/Service/SnowreportImportService.php +++ b/packages/ep_products/Classes/Service/SnowreportImportService.php @@ -23,25 +23,25 @@ class SnowreportImportService implements LoggerAwareInterface { use LoggerAwareTrait; - private const FEED_URL = 'https://www.skiresort-service.com/xml-feed/'; + private const string FEED_URL = 'https://www.skiresort-service.com/xml-feed/'; // A healthy response arrives in well under a second; an outage should fail visibly - private const HTTP_CONNECT_TIMEOUT = 5; - private const HTTP_TIMEOUT = 30; + private const int HTTP_CONNECT_TIMEOUT = 5; + private const int HTTP_TIMEOUT = 30; - private const SNOWREPORT_TABLE = 'tx_epproducts_domain_model_snowreport'; - private const WEBCAM_TABLE = 'tx_epproducts_domain_model_webcam'; + private const string SNOWREPORT_TABLE = 'tx_epproducts_domain_model_snowreport'; + private const string WEBCAM_TABLE = 'tx_epproducts_domain_model_webcam'; - private const TYPE_INT = 'int'; - private const TYPE_STRING = 'string'; - private const TYPE_BOOL = 'bool'; - private const TYPE_DATE = 'date'; + private const string TYPE_INT = 'int'; + private const string TYPE_STRING = 'string'; + private const string TYPE_BOOL = 'bool'; + private const string TYPE_DATE = 'date'; /** * XML element => column and type. XML has no types, so each field is declared here and converted * strictly; unexpected values are logged and fall back to the type's empty value. */ - private const MAP = [ + private const array MAP = [ 'region_id' => ['column' => 'vendor_uid', 'type' => self::TYPE_INT], 'region_name' => ['column' => 'region_name', 'type' => self::TYPE_STRING], 'region_offen' => ['column' => 'opened', 'type' => self::TYPE_BOOL], @@ -59,7 +59,7 @@ class SnowreportImportService implements LoggerAwareInterface 'datum_saisonende' => ['column' => 'season_end', 'type' => self::TYPE_DATE], ]; - private const BOOLEANS = [ + private const array BOOLEANS = [ 'true' => true, '1' => true, 'ja' => true, 'yes' => true, 'false' => false, '0' => false, 'nein' => false, 'no' => false, ]; @@ -270,7 +270,7 @@ class SnowreportImportService implements LoggerAwareInterface private function flagObsolete(string $table, array $currentVendorUids, array $validVendorUids, int $now): void { - $obsolete = array_values(array_diff(array_map('strval', $currentVendorUids), array_map('strval', $validVendorUids))); + $obsolete = array_values(array_diff(array_map(strval(...), $currentVendorUids), array_map(strval(...), $validVendorUids))); if ([] === $obsolete) { return; } @@ -314,7 +314,7 @@ class SnowreportImportService implements LoggerAwareInterface $this->conversionWarnings[sprintf('<%s> expected a date, got "%s".', $key, $raw)] = true; return 0; } - return (new \DateTime($raw))->getTimestamp(); + return new \DateTime($raw)->getTimestamp(); } throw new \InvalidArgumentException(sprintf('Unknown field type "%s" declared for <%s>.', $type, $key), 1758000000); diff --git a/packages/ep_products/Classes/Utility/TypeConversionUtility.php b/packages/ep_products/Classes/Utility/TypeConversionUtility.php index 2168d79..941db53 100644 --- a/packages/ep_products/Classes/Utility/TypeConversionUtility.php +++ b/packages/ep_products/Classes/Utility/TypeConversionUtility.php @@ -20,9 +20,7 @@ class TypeConversionUtility return (int) $value; } if (\is_array($value)) { - return array_map(function($item) { - return static::convertType($item); - }, $value); + return array_map(fn($item) => static::convertType($item), $value); } if (empty($value)) { return null; diff --git a/packages/ep_products/Configuration/RequestMiddlewares.php b/packages/ep_products/Configuration/RequestMiddlewares.php index 73016e7..2ac4e91 100644 --- a/packages/ep_products/Configuration/RequestMiddlewares.php +++ b/packages/ep_products/Configuration/RequestMiddlewares.php @@ -1,12 +1,13 @@ [ 'ep/staticsearch' => [ - 'target' => \EP\EpProducts\Middleware\StaticSearchParamsMiddleware::class, + 'target' => StaticSearchParamsMiddleware::class, 'before' => [ 'typo3/cms-frontend/page-resolver', ], diff --git a/packages/ep_products/ext_localconf.php b/packages/ep_products/ext_localconf.php index b82bb8e..2c57e48 100644 --- a/packages/ep_products/ext_localconf.php +++ b/packages/ep_products/ext_localconf.php @@ -1,4 +1,5 @@ 'time_micro', 'expirePeriod' => 180, ]; diff --git a/packages/ep_theme/Classes/Domain/Model/Page.php b/packages/ep_theme/Classes/Domain/Model/Page.php index cd74a2f..5152640 100644 --- a/packages/ep_theme/Classes/Domain/Model/Page.php +++ b/packages/ep_theme/Classes/Domain/Model/Page.php @@ -9,11 +9,13 @@ class Page extends AbstractEntity /** * @var int */ + #[\Override] protected ?int $uid = null; /** * @var int */ + #[\Override] protected ?int $pid = null; /** diff --git a/packages/ep_theme/Classes/Domain/Repository/PageRepository.php b/packages/ep_theme/Classes/Domain/Repository/PageRepository.php index 0d8e16e..ea95ef0 100644 --- a/packages/ep_theme/Classes/Domain/Repository/PageRepository.php +++ b/packages/ep_theme/Classes/Domain/Repository/PageRepository.php @@ -10,6 +10,7 @@ class PageRepository extends Repository /** * @var array */ + #[\Override] protected $defaultOrderings = [ 'sorting' => 'ASC', ]; diff --git a/packages/ep_theme/Classes/Form/AbstractMappedFinisher.php b/packages/ep_theme/Classes/Form/AbstractMappedFinisher.php index c0276db..ceb56a1 100644 --- a/packages/ep_theme/Classes/Form/AbstractMappedFinisher.php +++ b/packages/ep_theme/Classes/Form/AbstractMappedFinisher.php @@ -46,7 +46,7 @@ abstract class AbstractMappedFinisher extends AbstractFinisher $formValues = $this->finisherContext->getFormValues(); foreach ($this->parseMapping() as $remote => $local) { - if (false === array_key_exists($local, $formValues)) { + if (false === array_key_exists((string) $local, $formValues)) { continue; } @@ -130,13 +130,13 @@ abstract class AbstractMappedFinisher extends AbstractFinisher if ($value instanceof FileReference) { $resource = $value->getOriginalResource(); - return null !== $resource ? $resource->getName() : null; + return $resource?->getName(); } if (true === is_object($value)) { $this->logWarning('Form value skipped while collecting labeled values: unsupported type.', [ 'element' => $identifier, - 'class' => get_class($value), + 'class' => $value::class, ]); return null; diff --git a/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php b/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php index 7335626..18cf1b7 100644 --- a/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php +++ b/packages/ep_theme/Classes/Form/ApplicationApiFinisher.php @@ -31,7 +31,7 @@ class ApplicationApiFinisher extends AbstractMappedFinisher // without keeping a list of identifiers. Taken from the definition rather than // through TranslationService: a translated label would differ per site language. $payload['formLabel'] = '' !== $formLabel ? $formLabel : $formDefinition->getIdentifier(); - $payload['submittedAt'] = (new \DateTimeImmutable())->format(\DateTimeInterface::ATOM); + $payload['submittedAt'] = new \DateTimeImmutable()->format(\DateTimeInterface::ATOM); $payload = array_merge($payload, $this->resolvePageContext()); $payload['details'] = $this->buildLabeledValues(); diff --git a/packages/ep_theme/Classes/Middleware/CdnImageMiddleware.php b/packages/ep_theme/Classes/Middleware/CdnImageMiddleware.php index c339ebc..50c5537 100644 --- a/packages/ep_theme/Classes/Middleware/CdnImageMiddleware.php +++ b/packages/ep_theme/Classes/Middleware/CdnImageMiddleware.php @@ -32,11 +32,11 @@ use TYPO3\CMS\Core\Http\Stream; * * Registered after the page resolver, so the /api/* responses of ep_products never pass here. */ -final class CdnImageMiddleware implements MiddlewareInterface +final readonly class CdnImageMiddleware implements MiddlewareInterface { - private const PATTERN = '/(["\'])\/?(fileadmin|uploads|typo3temp\/assets\/(?:_processed_|images))/'; + private const string PATTERN = '/(["\'])\/?(fileadmin|uploads|typo3temp\/assets\/(?:_processed_|images))/'; - public function __construct(private readonly ExtensionConfiguration $extensionConfiguration) + public function __construct(private ExtensionConfiguration $extensionConfiguration) { } diff --git a/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php index 6795a0b..1f18922 100644 --- a/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/ContainerViewHelper.php @@ -19,6 +19,7 @@ class ContainerViewHelper extends AbstractViewHelper /** * @var bool */ + #[\Override] protected $escapeOutput = false; /** @@ -45,9 +46,7 @@ class ContainerViewHelper extends AbstractViewHelper public function render() { $content = $this->renderChildren(); - if ($content === null) { - $content = $this->arguments['content']; - } + $content ??= $this->arguments['content']; $classes = []; if (\in_array($this->arguments['data']['colPos'] ?? null, static::$columnsWithoutContainers, false)) { $classes[] = 'container'; diff --git a/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php index 0630a76..26b9911 100644 --- a/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/FilterSettingsEncoderViewHelper.php @@ -11,6 +11,7 @@ class FilterSettingsEncoderViewHelper extends AbstractViewHelper /** * @var bool */ + #[\Override] protected $escapeOutput = false; public function initializeArguments() @@ -29,9 +30,7 @@ class FilterSettingsEncoderViewHelper extends AbstractViewHelper public function render() { $argument = $this->renderChildren(); - if ($argument === null) { - $argument = $this->arguments['argument']; - } + $argument ??= $this->arguments['argument']; if ($argument === null) { return ''; } diff --git a/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php index c0bd277..b8df963 100644 --- a/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/GmapsUrlViewHelper.php @@ -12,6 +12,7 @@ class GmapsUrlViewHelper extends AbstractViewHelper /** * @var bool */ + #[\Override] protected $escapeOutput = false; const URLBASE = 'http://maps.google.com/maps?q=%s,%s&t=%s&z=%d'; diff --git a/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php index 4fd428a..060052c 100644 --- a/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/IconViewHelper.php @@ -11,6 +11,7 @@ class IconViewHelper extends AbstractTagBasedViewHelper /** * @var string */ + #[\Override] protected $tagName = 'svg'; public function initializeArguments() diff --git a/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php index d05529f..57c76a8 100644 --- a/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/RatingViewHelper.php @@ -11,6 +11,7 @@ class RatingViewHelper extends AbstractViewHelper /** * @var bool */ + #[\Override] protected $escapeOutput = false; public function initializeArguments() @@ -29,9 +30,7 @@ class RatingViewHelper extends AbstractViewHelper public function render() { $product = $this->renderChildren(); - if ($product === null) { - $product = $this->arguments['product']; - } + $product ??= $this->arguments['product']; $ratingAverage = $product->getRatingAverage(); $ratingVotesCount = $product->getRatingVotesCount(); $fullStars = floor($ratingAverage); diff --git a/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php index e3493c4..afa7f46 100644 --- a/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/ThemeClassesViewHelper.php @@ -24,7 +24,7 @@ class ThemeClassesViewHelper extends AbstractViewHelper public function render() { $themeKey = $this->arguments['key']; - if (!array_key_exists($themeKey, $this->arguments['classes'])) { + if (!array_key_exists((string) $themeKey, $this->arguments['classes'])) { return ''; } return $this->arguments['classes'][$themeKey]; diff --git a/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php b/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php index 932f2a4..72a95ae 100644 --- a/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php +++ b/packages/ep_theme/Classes/ViewHelpers/Uri/VideoViewHelper.php @@ -10,6 +10,7 @@ class VideoViewHelper extends AbstractViewHelper /** * @var bool */ + #[\Override] protected $escapeOutput = false; public function initializeArguments() diff --git a/packages/ep_theme/Configuration/RequestMiddlewares.php b/packages/ep_theme/Configuration/RequestMiddlewares.php index 5633005..91d5689 100644 --- a/packages/ep_theme/Configuration/RequestMiddlewares.php +++ b/packages/ep_theme/Configuration/RequestMiddlewares.php @@ -1,15 +1,19 @@ [ 'ep/cdn-images' => [ - 'target' => \EP\EpTheme\Middleware\CdnImageMiddleware::class, + 'target' => CdnImageMiddleware::class, 'after' => [ 'typo3/cms-frontend/page-resolver', ], ], 'ep/referrer' => [ - 'target' => \EP\EpTheme\Middleware\ReferrerMiddleware::class, + 'target' => ReferrerMiddleware::class, 'after' => [ 'typo3/cms-frontend/authentication', ], @@ -20,7 +24,7 @@ return [ // Outermost of the frontend stack after the site is known, so it also sees redirects // that core middlewares or exceptions (PropagateResponseException) produce 'ep/htmx-redirect' => [ - 'target' => \EP\EpTheme\Middleware\HtmxRedirectMiddleware::class, + 'target' => HtmxRedirectMiddleware::class, 'after' => [ 'typo3/cms-frontend/site', ], diff --git a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php index ab5a8c3..5a8dddf 100644 --- a/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php +++ b/packages/ep_theme/Configuration/TCA/Overrides/tt_content_element_container.php @@ -55,7 +55,7 @@ call_user_func(function () { $row[] = $config; } GeneralUtility::makeInstance(Registry::class)->configureContainer( - (new ContainerConfiguration($cType, $label, $description, [$row])) + new ContainerConfiguration($cType, $label, $description, [$row]) ->setIcon('tx_eptheme_gridelements_' . $icon) ); } diff --git a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php index 3cace51..a12a85d 100644 --- a/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php +++ b/packages/ep_theme/Configuration/TCA/tx_eptheme_domain_model_lineup.php @@ -113,7 +113,8 @@ return [ 'config' => [ 'type' => 'input', 'size' => 30, - 'eval' => 'trim,required' + 'eval' => 'trim', + 'required' => true ], ], 'description' => [ diff --git a/rector.php b/rector.php index ad3aa76..d52c758 100644 --- a/rector.php +++ b/rector.php @@ -27,6 +27,7 @@ return RectorConfig::configure() MigratePluginContentElementAndPluginSubtypesTCARector::class, ]) ->withPhpVersion(PhpVersion::PHP_85) + ->withPhpSets(php85: true) ->withSets([ Typo3SetList::GENERAL, Typo3LevelSetList::UP_TO_TYPO3_13,