chore: major code cleanup and type safety using phpstan
This commit is contained in:
1 parent
060cbea9da
commit
3da73d626b
103 files changed
+976
-1192
No files matched your search
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Admin\Field;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet;
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class BookingDataProcessor
|
||||
|
||||
// Parking service (for self-organized PKW transportation)
|
||||
$parkingServices = $booking->getAdditionalServicesForParticipantByGroup($index, Constants::TOKEN_PARKING);
|
||||
if (false === empty($parkingServices)) {
|
||||
if ([] !== $parkingServices) {
|
||||
$participantData->parking = true;
|
||||
$participantData->parkingService = reset($parkingServices);
|
||||
} else {
|
||||
@@ -320,7 +320,7 @@ class BookingDataProcessor
|
||||
|
||||
// Add purchase vouchers if any exist
|
||||
$purchaseVouchers = $this->mappingCollector->collectPurchaseVouchers($formData);
|
||||
if (false === empty($purchaseVouchers)) {
|
||||
if ([] !== $purchaseVouchers) {
|
||||
$payload['gutscheine']['gutschein'] = [];
|
||||
foreach ($purchaseVouchers as $code) {
|
||||
$payload['gutscheine']['gutschein'][] = [
|
||||
|
||||
@@ -43,7 +43,7 @@ class ServiceMappingCollector
|
||||
* Groups board, ski passes, rentals, rental insurance, courses, parking, and additional services
|
||||
* by service ID with their participant assignments (1-based).
|
||||
*
|
||||
* @return array<string, array<int>> Map of service ID to participant IDs
|
||||
* @return array<int, list<int>> Map of service ID to participant IDs
|
||||
*/
|
||||
public function collectServiceMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
@@ -99,7 +99,7 @@ class ServiceMappingCollector
|
||||
/**
|
||||
* Collects transportation service mappings.
|
||||
*
|
||||
* @return array<string, array<int>> Map of transportation service ID to participant IDs
|
||||
* @return array<int, list<int>> Map of transportation service ID to participant IDs
|
||||
*/
|
||||
public function collectTransportationMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
@@ -126,7 +126,7 @@ class ServiceMappingCollector
|
||||
* Collects the unified pickup selection which applies to both directions.
|
||||
* The API receives this as outbound pickup data.
|
||||
*
|
||||
* @return array<string, array<int>> Map of pickup ID to participant IDs
|
||||
* @return array<int, list<int>> Map of pickup ID to participant IDs
|
||||
*/
|
||||
public function collectPickupMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
@@ -148,7 +148,7 @@ class ServiceMappingCollector
|
||||
*
|
||||
* Collects the drop-off selection for the inbound/return direction.
|
||||
*
|
||||
* @return array<string, array<int>> Map of drop-off ID to participant IDs
|
||||
* @return array<int, list<int>> Map of drop-off ID to participant IDs
|
||||
*/
|
||||
public function collectDropOffMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
@@ -171,7 +171,7 @@ class ServiceMappingCollector
|
||||
* CRITICAL: Insurance data is only included in CREATE flow, not in UPDATE flow.
|
||||
* IMPORTANT: Excludes synthetic "no insurance" option from BPN transmission.
|
||||
*
|
||||
* @return array<string, array<int>> Map of insurance ID to participant IDs
|
||||
* @return array<string, list<int>> Map of insurance ID to participant IDs
|
||||
*/
|
||||
public function collectInsuranceMappings(BookingDto $bookingDto): array
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Exception;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Exception;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Form\ChoiceLoader;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Form;
|
||||
|
||||
|
||||
@@ -182,8 +182,15 @@ class Insurance
|
||||
$urls = [];
|
||||
|
||||
foreach ($this->containedInsurances as $containedInsurance) {
|
||||
if (null !== $containedInsurance->{$urlProperty}) {
|
||||
$urls[] = $containedInsurance->{$urlProperty};
|
||||
$url = match ($urlProperty) {
|
||||
'urlInfo' => $containedInsurance->urlInfo,
|
||||
'urlProductInfo' => $containedInsurance->urlProductInfo,
|
||||
'urlTerms' => $containedInsurance->urlTerms,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (null !== $url) {
|
||||
$urls[] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Model;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Traits;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Utility;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\Utility;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlLoader;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\BusProNet\XmlParser;
|
||||
|
||||
@@ -15,10 +16,11 @@ class ProductsParser extends AbstractParser
|
||||
$result
|
||||
->filterXPath('//produkte/produkt')
|
||||
->each(function (Crawler $node) use (&$products) {
|
||||
if (false === empty($node->attr('code'))) {
|
||||
$code = $node->attr('code');
|
||||
if (null !== $code && '' !== $code) {
|
||||
$product = new Product();
|
||||
$product->id = (int) $node->attr('id');
|
||||
$product->code = (string) $node->attr('code');
|
||||
$product->code = $code;
|
||||
$product->name = (string) $node->attr('bezeichnung');
|
||||
|
||||
$products[$product->id] = $product;
|
||||
|
||||
@@ -50,8 +50,11 @@ class BpnFetchTravelCommand extends Command
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$travelId = (int) $input->getArgument('travel-id');
|
||||
$filename = $input->getOption('filename') ?? sprintf('Ziel_%d.xml', $travelId);
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$filenameOption = $input->getOption('filename');
|
||||
$filename = is_string($filenameOption) && '' !== $filenameOption
|
||||
? $filenameOption
|
||||
: sprintf('Ziel_%d.xml', $travelId);
|
||||
$dryRun = true === $input->getOption('dry-run');
|
||||
|
||||
$io->text(sprintf('Fetching travel data for product ID %d...', $travelId));
|
||||
|
||||
|
||||
@@ -39,7 +39,14 @@ class BpnReplayCommand extends Command
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$filePath = $input->getArgument('file');
|
||||
$filePathArgument = $input->getArgument('file');
|
||||
if (!is_string($filePathArgument) || '' === $filePathArgument) {
|
||||
$io->error('Invalid file path argument.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$filePath = $filePathArgument;
|
||||
if (false === file_exists($filePath)) {
|
||||
$io->error(sprintf('File not found: %s', $filePath));
|
||||
|
||||
@@ -84,8 +91,9 @@ class BpnReplayCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$outputFile = $input->getOption('output');
|
||||
if (null !== $outputFile) {
|
||||
$outputFileOption = $input->getOption('output');
|
||||
if (is_string($outputFileOption) && '' !== $outputFileOption) {
|
||||
$outputFile = $outputFileOption;
|
||||
if (false === file_put_contents($outputFile, $response)) {
|
||||
$io->error(sprintf('Failed to write response to: %s', $outputFile));
|
||||
|
||||
@@ -139,6 +147,8 @@ class BpnReplayCommand extends Command
|
||||
return $xml;
|
||||
}
|
||||
|
||||
return $doc->saveXML() ?: $xml;
|
||||
$formattedXml = $doc->saveXML();
|
||||
|
||||
return false === $formattedXml ? $xml : $formattedXml;
|
||||
}
|
||||
}
|
||||
@@ -65,8 +65,8 @@ class BpnXmlSyncCommand extends Command
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$force = $input->getOption('force');
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$force = true === $input->getOption('force');
|
||||
$dryRun = true === $input->getOption('dry-run');
|
||||
|
||||
$remoteInfo = $this->readRemoteInfo();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class DraftBackfillBookingNumberCommand extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$dryRun = true === $input->getOption('dry-run');
|
||||
|
||||
$drafts = $this->draftRepository->findBy(['bookingNumber' => null]);
|
||||
|
||||
@@ -80,7 +80,7 @@ class DraftBackfillBookingNumberCommand extends Command
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
if (true === $dryRun) {
|
||||
$io->text(sprintf(
|
||||
'[DRY RUN] Would set booking number %d for draft %d (bookingId %d)',
|
||||
$bookingNumber,
|
||||
@@ -108,7 +108,7 @@ class DraftBackfillBookingNumberCommand extends Command
|
||||
]
|
||||
);
|
||||
|
||||
if ($dryRun) {
|
||||
if (true === $dryRun) {
|
||||
$io->note('Dry run - no changes were made.');
|
||||
} else {
|
||||
$io->success(sprintf('Updated %d draft(s).', $updated));
|
||||
|
||||
@@ -38,9 +38,9 @@ class DraftCleanupCommand extends Command
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$dryRun = $input->getOption('dry-run');
|
||||
$dryRun = true === $input->getOption('dry-run');
|
||||
|
||||
if ($dryRun) {
|
||||
if (true === $dryRun) {
|
||||
$expiredDrafts = $this->draftRepository->createQueryBuilder('d')
|
||||
->where('d.travelDate < :today')
|
||||
->setParameter('today', new \DateTimeImmutable('today'))
|
||||
|
||||
@@ -38,9 +38,12 @@ class DraftInspectCommand extends Command
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$days = $input->getOption('days');
|
||||
$bookingId = $input->getOption('booking-id');
|
||||
$shouldDelete = $input->getOption('delete');
|
||||
$daysOption = $input->getOption('days');
|
||||
$bookingIdOption = $input->getOption('booking-id');
|
||||
$shouldDelete = true === $input->getOption('delete');
|
||||
|
||||
$days = is_numeric($daysOption) ? (int) $daysOption : null;
|
||||
$bookingId = is_numeric($bookingIdOption) ? (int) $bookingIdOption : null;
|
||||
|
||||
$qb = $this->draftRepository->createQueryBuilder('d')
|
||||
->join('d.user', 'u')
|
||||
@@ -48,14 +51,14 @@ class DraftInspectCommand extends Command
|
||||
->orderBy('d.updatedAt', 'DESC');
|
||||
|
||||
if (null !== $days) {
|
||||
$cutoffDate = new \DateTimeImmutable(sprintf('-%d days', (int) $days));
|
||||
$cutoffDate = new \DateTimeImmutable(sprintf('-%d days', $days));
|
||||
$qb->andWhere('d.updatedAt < :cutoff')
|
||||
->setParameter('cutoff', $cutoffDate);
|
||||
}
|
||||
|
||||
if (null !== $bookingId) {
|
||||
$qb->andWhere('d.bookingId = :bookingId')
|
||||
->setParameter('bookingId', (int) $bookingId);
|
||||
->setParameter('bookingId', $bookingId);
|
||||
}
|
||||
|
||||
$drafts = $qb->getQuery()->getResult();
|
||||
@@ -97,7 +100,7 @@ class DraftInspectCommand extends Command
|
||||
'Drafts are preserved when: user cancels, API rejects submission, or user simply leaves',
|
||||
]);
|
||||
|
||||
if ($shouldDelete) {
|
||||
if (true === $shouldDelete) {
|
||||
if (false === $io->confirm(sprintf('Are you sure you want to delete %d draft(s)?', \count($drafts)), false)) {
|
||||
$io->warning('Deletion cancelled.');
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
@@ -26,13 +27,13 @@ class LastUpdateController extends AbstractController
|
||||
$lines = explode("\n", $file);
|
||||
$line = trim($lines[0]);
|
||||
|
||||
if (true === empty($line)) {
|
||||
if ('' === $line) {
|
||||
$lastUploadAt = null;
|
||||
} else {
|
||||
$lastUploadAt = \DateTime::createFromFormat('d.m.Y H:i:s', $line);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json(['timestamp' => $lastUploadAt->format('Y-m-d H:i:s')]);
|
||||
return $this->json(['timestamp' => $lastUploadAt?->format('Y-m-d H:i:s')]);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
@@ -64,7 +65,9 @@ class TravelController extends AbstractController
|
||||
$dateCode = (new DateCodeUtility())->sanitize($dateCode);
|
||||
|
||||
$dateId = $this->travelDataService->mapDateCodeToId($dateCode);
|
||||
$hotelId = $hotelCode ? $this->travelDataService->mapHotelCodeToId($hotelCode) : null;
|
||||
$hotelId = null !== $hotelCode && '' !== $hotelCode
|
||||
? $this->travelDataService->mapHotelCodeToId($hotelCode)
|
||||
: null;
|
||||
|
||||
if (null === $dateId) {
|
||||
return $this->json(['message' => 'Not found'], Response::HTTP_NOT_FOUND);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
@@ -80,7 +81,7 @@ class UserinfoController extends AbstractController
|
||||
|
||||
// filter claims by provided scopes
|
||||
foreach ($scopes as $scope) {
|
||||
if (false === in_array($scope, $keys)) {
|
||||
if (false === in_array($scope, $keys, true)) {
|
||||
continue;
|
||||
}
|
||||
$claims[$scope] = $allClaims[$scope];
|
||||
|
||||
@@ -53,7 +53,8 @@ class Step1Controller extends AbstractController
|
||||
$oldRoomSelectionSnapshot = $this->bookingSessionService->getOrCreateBaselineSnapshot($request, $bookingCreateDto);
|
||||
|
||||
// Validate step access - allow step 1 or redirect to current step
|
||||
if ($redirect = $this->validateStepAccess($bookingCreateDto, 1)) {
|
||||
$redirect = $this->validateStepAccess($bookingCreateDto, 1);
|
||||
if (null !== $redirect) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ class Step4Controller extends AbstractController
|
||||
$bookingCreateDto = $result;
|
||||
|
||||
// Validate step access
|
||||
if ($redirect = $this->validateStepAccess($bookingCreateDto, 4)) {
|
||||
$redirect = $this->validateStepAccess($bookingCreateDto, 4);
|
||||
if (null !== $redirect) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
@@ -218,6 +219,22 @@ class Step4Controller extends AbstractController
|
||||
return false !== filter_var($normalizedEmail, FILTER_VALIDATE_EMAIL) ? $normalizedEmail : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $context
|
||||
*/
|
||||
private function handleApiError(
|
||||
string $logMessage,
|
||||
array $context,
|
||||
string $flashMessage,
|
||||
BookingDto $bookingCreateDto,
|
||||
FormInterface $form,
|
||||
): Response {
|
||||
$this->logger->error($logMessage, $context);
|
||||
$this->addFlash('error', $flashMessage);
|
||||
|
||||
return $this->renderStepForm($bookingCreateDto, $form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears travel data and availability cache after successful booking.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Booking;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\DependencyInjection\Env;
|
||||
|
||||
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
|
||||
final class CsvIntEnvVarProcessor implements EnvVarProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @return array<int, int>
|
||||
*/
|
||||
public function getEnv(string $prefix, string $name, \Closure $getEnv): array
|
||||
{
|
||||
$raw = $getEnv($name);
|
||||
|
||||
if (!\is_string($raw)) {
|
||||
throw new RuntimeException(sprintf('The "csv_int" env var processor expects a string, got "%s" for "%s".', get_debug_type($raw), $name));
|
||||
}
|
||||
|
||||
$values = array_map('trim', explode(',', $raw));
|
||||
|
||||
if ([] !== array_filter($values, static fn (string $value): bool => '' === $value)) {
|
||||
throw new RuntimeException(sprintf('Environment variable "%s" contains empty values and cannot be processed as csv_int.', $name));
|
||||
}
|
||||
|
||||
$integers = array_map(static function (string $value) use ($name): int {
|
||||
if (1 !== preg_match('/^[+-]?\d+$/', $value)) {
|
||||
throw new RuntimeException(sprintf('Environment variable "%s" contains non-integer value "%s".', $name, $value));
|
||||
}
|
||||
|
||||
return (int) $value;
|
||||
}, $values);
|
||||
|
||||
return $integers;
|
||||
}
|
||||
|
||||
public static function getProvidedTypes(): array
|
||||
{
|
||||
return [
|
||||
'csv_int' => 'array',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (false === empty($attributes)) {
|
||||
if ([] !== $attributes) {
|
||||
$stateModifications['attr'] = $attributes;
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||
*/
|
||||
public function hasStateConditions(string $fieldName): bool
|
||||
{
|
||||
return isset($this->fieldStateConditions[$fieldName]) && !empty($this->fieldStateConditions[$fieldName]);
|
||||
return isset($this->fieldStateConditions[$fieldName]) && [] !== $this->fieldStateConditions[$fieldName];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +210,7 @@ abstract class AbstractFieldStateProvider implements FieldStateProviderInterface
|
||||
|
||||
foreach (array_keys($this->fieldStateConditions) as $fieldName) {
|
||||
$fieldState = $this->getFieldState($fieldName, $bookingDto, $participantIndex, $formData);
|
||||
if (!empty($fieldState)) {
|
||||
if ([] !== $fieldState) {
|
||||
$allStates[$fieldName] = $fieldState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
*/
|
||||
protected function normalizeEmptyValue(mixed $value): mixed
|
||||
{
|
||||
return empty($value) ? null : $value;
|
||||
return $this->isEmptyValue($value) ? null : $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,13 +146,24 @@ abstract class AbstractParticipantFieldHandler implements ParticipantFieldHandle
|
||||
*/
|
||||
protected function normalizeIntValue(mixed $value): ?int
|
||||
{
|
||||
if (empty($value)) {
|
||||
if ($this->isEmptyValue($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_numeric($value) ? (int) $value : null;
|
||||
}
|
||||
|
||||
private function isEmptyValue(mixed $value): bool
|
||||
{
|
||||
return null === $value
|
||||
|| false === $value
|
||||
|| 0 === $value
|
||||
|| 0.0 === $value
|
||||
|| '' === $value
|
||||
|| '0' === $value
|
||||
|| [] === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a checkbox/form toggle value to a boolean.
|
||||
*
|
||||
|
||||
@@ -37,7 +37,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
private const OPERATOR_OR = 'OR';
|
||||
private const OPERATOR_NOT = 'NOT';
|
||||
|
||||
/** @var FieldConditionInterface[] */
|
||||
/** @var non-empty-list<FieldConditionInterface> */
|
||||
private readonly array $conditions;
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
*/
|
||||
public static function and(FieldConditionInterface ...$conditions): self
|
||||
{
|
||||
if (empty($conditions)) {
|
||||
if ([] === $conditions) {
|
||||
throw new \InvalidArgumentException('AND condition requires at least one condition');
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ class CompositeCondition implements FieldConditionInterface
|
||||
*/
|
||||
public static function or(FieldConditionInterface ...$conditions): self
|
||||
{
|
||||
if (empty($conditions)) {
|
||||
if ([] === $conditions) {
|
||||
throw new \InvalidArgumentException('OR condition requires at least one condition');
|
||||
}
|
||||
|
||||
@@ -167,6 +167,8 @@ class CompositeCondition implements FieldConditionInterface
|
||||
*
|
||||
* Returns false as soon as any condition evaluates to false,
|
||||
* avoiding unnecessary evaluation of remaining conditions.
|
||||
*
|
||||
* @param array<string, mixed> $formData
|
||||
*/
|
||||
/** @param array<string, mixed> $formData */
|
||||
private function evaluateAnd(BookingDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
@@ -185,6 +187,8 @@ class CompositeCondition implements FieldConditionInterface
|
||||
*
|
||||
* Returns true as soon as any condition evaluates to true,
|
||||
* avoiding unnecessary evaluation of remaining conditions.
|
||||
*
|
||||
* @param array<string, mixed> $formData
|
||||
*/
|
||||
/** @param array<string, mixed> $formData */
|
||||
private function evaluateOr(BookingDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
@@ -200,6 +204,8 @@ class CompositeCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Evaluates NOT logic by inverting the result of the single condition.
|
||||
*
|
||||
* @param array<string, mixed> $formData
|
||||
*/
|
||||
/** @param array<string, mixed> $formData */
|
||||
private function evaluateNot(BookingDto $bookingDto, int $participantIndex, array $formData): bool
|
||||
@@ -221,6 +227,8 @@ class CompositeCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Validates condition count based on operator requirements.
|
||||
*
|
||||
* @param list<FieldConditionInterface> $conditions
|
||||
*/
|
||||
/** @param list<mixed> $conditions */
|
||||
private function validateConditionCount(string $operator, array $conditions): void
|
||||
|
||||
@@ -174,6 +174,8 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Retrieves field value from form data or participant data.
|
||||
*
|
||||
* @param array<string, mixed> $formData
|
||||
*/
|
||||
/** @param array<string, mixed> $formData */
|
||||
private function getFieldValue(array $formData, int $participantIndex, BookingDto $bookingDto): mixed
|
||||
@@ -186,7 +188,9 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
// Then check participant DTO data
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
if (null !== $participant && property_exists($participant, $this->fieldName)) {
|
||||
return $participant->{$this->fieldName};
|
||||
$participantValues = get_object_vars($participant);
|
||||
|
||||
return $participantValues[$this->fieldName] ?? null;
|
||||
}
|
||||
|
||||
// Finally check booking-level form data
|
||||
@@ -212,6 +216,8 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Checks if field value is in array of expected values.
|
||||
*
|
||||
* @param array<int, mixed> $expectedValues
|
||||
*/
|
||||
/** @param list<mixed> $expectedValues */
|
||||
private function compareIn(mixed $fieldValue, array $expectedValues): bool
|
||||
@@ -230,7 +236,7 @@ class FieldValueCondition implements FieldConditionInterface
|
||||
*/
|
||||
private function isEmpty(mixed $value): bool
|
||||
{
|
||||
return null === $value || '' === $value || (is_array($value) && empty($value));
|
||||
return null === $value || '' === $value || [] === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -33,7 +33,7 @@ class RentalInsuranceAvailableCondition implements FieldConditionInterface
|
||||
true
|
||||
);
|
||||
|
||||
return false === empty($services);
|
||||
return [] !== $services;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,10 @@ class RoomSelectionCondition implements FieldConditionInterface
|
||||
*/
|
||||
public function __construct(array $requiredRoomCodes, string $matchMode = self::MATCH_MODE_PREFIX)
|
||||
{
|
||||
if (!in_array($matchMode, [self::MATCH_MODE_PREFIX, self::MATCH_MODE_EXACT], true)) {
|
||||
throw new \InvalidArgumentException(sprintf('Invalid match mode "%s".', $matchMode));
|
||||
}
|
||||
|
||||
$this->requiredRoomCodes = array_map('strtolower', $requiredRoomCodes);
|
||||
$this->matchMode = $matchMode;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Form\Service\Condition;
|
||||
|
||||
use App\BusProNet\Model\Service;
|
||||
use App\Form\Model\BookingDto;
|
||||
use App\Form\Model\ParticipantDto;
|
||||
use App\Form\Service\Contract\FieldConditionInterface;
|
||||
|
||||
/**
|
||||
@@ -103,7 +104,7 @@ class ServiceSubTypeCondition implements FieldConditionInterface
|
||||
{
|
||||
$subTypeDescription = is_array($this->expectedSubType)
|
||||
? '['.implode(', ', $this->expectedSubType).']'
|
||||
: (string) $this->expectedSubType;
|
||||
: $this->expectedSubType;
|
||||
|
||||
return match ($this->operator) {
|
||||
self::OPERATOR_EQUALS => sprintf('Service "%s" sub-type equals %s', $this->serviceFieldName, $subTypeDescription),
|
||||
@@ -160,6 +161,8 @@ class ServiceSubTypeCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Retrieves service from form data or participant data.
|
||||
*
|
||||
* @param array<string, mixed> $formData
|
||||
*/
|
||||
/** @param array<string, mixed> $formData */
|
||||
private function getService(array $formData, int $participantIndex, BookingDto $bookingDto): ?Service
|
||||
@@ -176,15 +179,30 @@ class ServiceSubTypeCondition implements FieldConditionInterface
|
||||
|
||||
// Then check participant DTO data
|
||||
$participant = $bookingDto->getParticipant($participantIndex);
|
||||
if (null !== $participant && property_exists($participant, $this->serviceFieldName)) {
|
||||
return $participant->{$this->serviceFieldName};
|
||||
if (null !== $participant) {
|
||||
return $this->getServiceFromParticipant($participant);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getServiceFromParticipant(ParticipantDto $participant): ?Service
|
||||
{
|
||||
return match ($this->serviceFieldName) {
|
||||
'skiPass' => $participant->skiPass,
|
||||
'rentalInsurance' => $participant->rentalInsurance,
|
||||
'transportationOutbound' => $participant->transportationOutbound,
|
||||
'transportationInbound' => $participant->transportationInbound,
|
||||
'veg' => $participant->veg,
|
||||
'parkingService' => $participant->parkingService,
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if sub-type is in the expected array.
|
||||
*
|
||||
* @param string|list<string> $expectedSubTypes
|
||||
*/
|
||||
/** @param string|list<string> $expectedSubTypes */
|
||||
private function isSubTypeIn(string $actualSubType, string|array $expectedSubTypes): bool
|
||||
@@ -215,6 +233,8 @@ class ServiceSubTypeCondition implements FieldConditionInterface
|
||||
|
||||
/**
|
||||
* Validates expected sub-type based on operator requirements.
|
||||
*
|
||||
* @param string|list<string> $expectedSubType
|
||||
*/
|
||||
/** @param string|list<string> $expectedSubType */
|
||||
private function validateExpectedSubType(string $operator, string|array $expectedSubType): void
|
||||
|
||||
@@ -274,7 +274,8 @@ class ParticipantFieldHandlerRegistry
|
||||
continue;
|
||||
}
|
||||
|
||||
$dtoValue = $participant->{$fieldName};
|
||||
$participantValues = get_object_vars($participant);
|
||||
$dtoValue = $participantValues[$fieldName] ?? null;
|
||||
$submittedValue = $participantData[$fieldName];
|
||||
|
||||
// Check if the DTO value differs from submitted value
|
||||
@@ -427,7 +428,7 @@ class ParticipantFieldHandlerRegistry
|
||||
}
|
||||
|
||||
// Process handlers in dependency order
|
||||
while (false === empty($queue)) {
|
||||
while ([] !== $queue) {
|
||||
$current = array_shift($queue);
|
||||
$result[] = $current;
|
||||
|
||||
@@ -517,7 +518,8 @@ class ParticipantFieldHandlerRegistry
|
||||
// Only sync fields that were in the original submission
|
||||
// This prevents adding extra fields that would cause validation errors
|
||||
if (property_exists($participant, $fieldName) && array_key_exists($fieldName, $participantData)) {
|
||||
$dtoValue = $participant->{$fieldName};
|
||||
$participantValues = get_object_vars($participant);
|
||||
$dtoValue = $participantValues[$fieldName] ?? null;
|
||||
$participantData[$fieldName] = $this->convertDtoValueToSubmittedFormat($dtoValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ class ParticipantParkingFieldHandler extends AbstractParticipantFieldHandler
|
||||
Constants::TOKEN_PARKING
|
||||
);
|
||||
|
||||
if (empty($parkingServices)) {
|
||||
if ([] === $parkingServices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class BpnXmlAnonymizer
|
||||
continue;
|
||||
}
|
||||
|
||||
$personId = $this->resolvePersonId($personNode, (int) $index);
|
||||
$personId = $this->resolvePersonId($personNode, $index);
|
||||
$identity = $this->getOrCreateIdentity($personId);
|
||||
|
||||
$this->replaceNameFields($personNode, $identity);
|
||||
@@ -96,7 +96,7 @@ class BpnXmlAnonymizer
|
||||
}
|
||||
|
||||
if (true === $personNode->hasAttribute('id')) {
|
||||
$attributeValue = trim((string) $personNode->getAttribute('id'));
|
||||
$attributeValue = trim($personNode->getAttribute('id'));
|
||||
if ('' !== $attributeValue) {
|
||||
return sprintf('attr-id:%s', $attributeValue);
|
||||
}
|
||||
@@ -115,7 +115,7 @@ class BpnXmlAnonymizer
|
||||
$nameValues[] = trim($nameNode->textContent);
|
||||
}
|
||||
if (true === $personNode->hasAttribute('name')) {
|
||||
$nameValues[] = trim((string) $personNode->getAttribute('name'));
|
||||
$nameValues[] = trim($personNode->getAttribute('name'));
|
||||
}
|
||||
|
||||
$firstNameNode = $this->getDirectChild($personNode, 'vorname');
|
||||
@@ -123,7 +123,7 @@ class BpnXmlAnonymizer
|
||||
$nameValues[] = trim($firstNameNode->textContent);
|
||||
}
|
||||
if (true === $personNode->hasAttribute('vorname')) {
|
||||
$nameValues[] = trim((string) $personNode->getAttribute('vorname'));
|
||||
$nameValues[] = trim($personNode->getAttribute('vorname'));
|
||||
}
|
||||
|
||||
$communicationNode = $this->getDirectChild($personNode, 'kommunikation');
|
||||
|
||||
@@ -136,7 +136,7 @@ class InsuranceManager
|
||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking, $travelPrice);
|
||||
|
||||
// Return the first eligible insurance (they should all be equivalent for the same type)
|
||||
return !empty($eligibleInsurances) ? array_values($eligibleInsurances)[0] : null;
|
||||
return [] !== $eligibleInsurances ? array_values($eligibleInsurances)[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +168,7 @@ class InsuranceManager
|
||||
foreach ($booking->getParticipants() as $index => $participant) {
|
||||
$travelPrice = $participantPrices[$index] ?? 0.0;
|
||||
$eligibleInsurances = $this->getEligibleInsurances($sameTypeInsurances, $participant, $booking, $travelPrice);
|
||||
$assignments[$index] = !empty($eligibleInsurances) ? array_values($eligibleInsurances)[0] : null;
|
||||
$assignments[$index] = [] !== $eligibleInsurances ? array_values($eligibleInsurances)[0] : null;
|
||||
}
|
||||
|
||||
return $assignments;
|
||||
|
||||
@@ -70,7 +70,7 @@ class ParticipantEligibilityChecker
|
||||
fn (Service $service) => $this->isSkiPassAvailableForParticipant($service, $bookingDto, $participantIndex)
|
||||
);
|
||||
|
||||
return !empty($availableSkiPasses);
|
||||
return [] !== $availableSkiPasses;
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
@@ -282,6 +282,14 @@ class AppRuntime implements RuntimeExtensionInterface
|
||||
);
|
||||
}
|
||||
|
||||
private function renderIcon(Environment $environment, string $icon, ?string $classes): string
|
||||
{
|
||||
return $environment->render('_partials/_icon.html.twig', [
|
||||
'icon' => $icon,
|
||||
'class' => $classes ?? 'w-4 h-4',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate field state provider based on booking mode.
|
||||
*
|
||||
|
||||
@@ -63,7 +63,7 @@ class MandatoryAdditionalServicesSelectedValidator extends ConstraintValidator
|
||||
}
|
||||
);
|
||||
|
||||
if (true === empty($mandatoryAdditionalServices)) {
|
||||
if ([] === $mandatoryAdditionalServices) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class ParticipantValidator extends ConstraintValidator
|
||||
public function assertBodyDimensionsWhenRentalsSelected(ParticipantDto $participant): void
|
||||
{
|
||||
// Check if any rental services are selected
|
||||
if (true === empty($participant->rentals)) {
|
||||
if ([] === $participant->rentals) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user