diff --git a/.github/workflows/combined-workflow.yaml b/.github/workflows/combined-workflow.yaml index f946739ba..3d4caff0d 100644 --- a/.github/workflows/combined-workflow.yaml +++ b/.github/workflows/combined-workflow.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest if: | always() && - (needs.call-code-quality.result == 'success' || needs.call-code-quality.result == 'skipped') && + needs.call-code-quality.result == 'success' && needs.call-unit-tests.result == 'success' steps: - run: echo "All required checks passed" \ No newline at end of file diff --git a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ExceptionService.php b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ExceptionService.php index 4125b3094..936718718 100644 --- a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ExceptionService.php +++ b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ExceptionService.php @@ -21,112 +21,116 @@ public static function getLanguageContext(): ?string private static function getError(string $key): array { - return ErrorMessages::get($key, self::$currentLanguage); + return ErrorMessages::get($key, self::$currentLanguage); } + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @TODO: Consider using a strategy pattern or error handler chain to reduce method complexity + */ public static function handleException(\Exception $e): never { $exceptionName = json_decode(json_encode($e), true)['template'] ?? null; $error = null; switch ($exceptionName) { - // Process exceptions + // Process exceptions case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotFound': - $error = self::getError('appointmentNotFound'); + $error = self::getError('appointmentNotFound'); break; case 'BO\\Zmsapi\\Exception\\Process\\AuthKeyMatchFailed': - $error = self::getError('authKeyMismatch'); + $error = self::getError('authKeyMismatch'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessAlreadyCalled': - $error = self::getError('processAlreadyCalled'); + $error = self::getError('processAlreadyCalled'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotReservedAnymore': - $error = self::getError('processNotReservedAnymore'); + $error = self::getError('processNotReservedAnymore'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotPreconfirmedAnymore': - $error = self::getError('processNotPreconfirmedAnymore'); + $error = self::getError('processNotPreconfirmedAnymore'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessDeleteFailed': - $error = self::getError('processDeleteFailed'); + $error = self::getError('processDeleteFailed'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessInvalid': - $error = self::getError('processInvalid'); + $error = self::getError('processInvalid'); break; case 'BO\\Zmsapi\\Exception\\Process\\ProcessAlreadyExists': - $error = self::getError('processAlreadyExists'); + $error = self::getError('processAlreadyExists'); break; case 'BO\\Zmsapi\\Exception\\Process\\EmailRequired': - $error = self::getError('emailIsRequired'); + $error = self::getError('emailIsRequired'); break; case 'BO\\Zmsapi\\Exception\\Process\\TelephoneRequired': - $error = self::getError('telephoneIsRequired'); + $error = self::getError('telephoneIsRequired'); break; case 'BO\\Zmsapi\\Exception\\Process\\MoreThanAllowedAppointmentsPerMail': - $error = self::getError('tooManyAppointmentsWithSameMail'); + $error = self::getError('tooManyAppointmentsWithSameMail'); break; case 'BO\\Zmsapi\\Exception\\Process\\PreconfirmationExpired': - $error = self::getError('preconfirmationExpired'); + $error = self::getError('preconfirmationExpired'); break; case 'BO\\Zmsapi\\Exception\\Process\\ApiclientInvalid': - $error = self::getError('invalidApiClient'); + $error = self::getError('invalidApiClient'); break; -// Calendar exceptions + // Calendar exceptions case 'BO\\Zmsapi\\Exception\\Calendar\\InvalidFirstDay': - $error = self::getError('invalidDateRange'); + $error = self::getError('invalidDateRange'); break; case 'BO\\Zmsapi\\Exception\\Calendar\\AppointmentsMissed': - $error = self::getError('noAppointmentsAtLocation'); + $error = self::getError('noAppointmentsAtLocation'); break; -// Other entity exceptions + // Other entity exceptions case 'BO\\Zmsapi\\Exception\\Department\\DepartmentNotFound': - $error = self::getError('departmentNotFound'); + $error = self::getError('departmentNotFound'); break; case 'BO\\Zmsapi\\Exception\\Mail\\MailNotFound': - $error = self::getError('mailNotFound'); + $error = self::getError('mailNotFound'); break; case 'BO\\Zmsapi\\Exception\\Organisation\\OrganisationNotFound': - $error = self::getError('organisationNotFound'); + $error = self::getError('organisationNotFound'); break; case 'BO\\Zmsapi\\Exception\\Provider\\ProviderNotFound': - $error = self::getError('providerNotFound'); + $error = self::getError('providerNotFound'); break; case 'BO\\Zmsapi\\Exception\\Request\\RequestNotFound': - $error = self::getError('requestNotFound'); + $error = self::getError('requestNotFound'); break; case 'BO\\Zmsapi\\Exception\\Scope\\ScopeNotFound': - $error = self::getError('scopeNotFound'); + $error = self::getError('scopeNotFound'); break; case 'BO\\Zmsapi\\Exception\\Source\\SourceNotFound': - $error = self::getError('sourceNotFound'); + $error = self::getError('sourceNotFound'); break; -// Use original message for unmapped exceptions + // Use original message for unmapped exceptions default: - $error = [ - 'errorCode' => $exceptionName ?? 'unknown', - 'errorMessage' => $e->getMessage(), - 'statusCode' => $e->getCode() ?: 500 - ]; + $error = [ + 'errorCode' => $exceptionName ?? 'unknown', + 'errorMessage' => $e->getMessage(), + 'statusCode' => $e->getCode() ?: 500 + ]; } throw new \RuntimeException($error['errorCode'] . ': ' . $error['errorMessage'], $error['statusCode'], $e); diff --git a/zmscitizenapi/src/Zmscitizenapi/Services/Core/MapperService.php b/zmscitizenapi/src/Zmscitizenapi/Services/Core/MapperService.php index 8e635c291..19c96a0e8 100644 --- a/zmscitizenapi/src/Zmscitizenapi/Services/Core/MapperService.php +++ b/zmscitizenapi/src/Zmscitizenapi/Services/Core/MapperService.php @@ -47,6 +47,11 @@ public static function mapScopeForProvider(int $providerId, ?ThinnedScopeList $s return $matchingScope; } + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO: Extract mapping logic into specialized mapper classes for each entity type + */ public static function mapOfficesWithScope(ProviderList $providerList): OfficeList { $offices = []; @@ -91,10 +96,10 @@ public static function mapServicesWithCombinations(RequestList $requestList, Req usort($requestArray, function ($a, $b) { return $a->getId() <=> $b->getId(); - // Sorting by service ID (ascending order) + // Sorting by service ID (ascending order) }); foreach ($requestArray as $service) { - /** @var array> $serviceCombinations */ + /** @var array> $serviceCombinations */ $serviceCombinations = []; $combinableData = $service->getAdditionalData()['combinable'] ?? []; foreach ($combinableData as $combinationServiceId) { @@ -140,6 +145,11 @@ public static function scopeToThinnedScope(Scope $scope): ThinnedScope return new ThinnedScope(id: (int) ($scope->id ?? 0), provider: $thinnedProvider, shortName: $scope->shortName ?? null, telephoneActivated: isset($scope->data['telephoneActivated']) ? (bool) $scope->data['telephoneActivated'] : null, telephoneRequired: isset($scope->data['telephoneRequired']) ? (bool) $scope->data['telephoneRequired'] : null, customTextfieldActivated: isset($scope->data['customTextfieldActivated']) ? (bool) $scope->data['customTextfieldActivated'] : null, customTextfieldRequired: isset($scope->data['customTextfieldRequired']) ? (bool) $scope->data['customTextfieldRequired'] : null, customTextfieldLabel: $scope->data['customTextfieldLabel'] ?? null, captchaActivatedRequired: isset($scope->data['captchaActivatedRequired']) ? (bool) $scope->data['captchaActivatedRequired'] : null, displayInfo: $scope->data['displayInfo'] ?? null); } + /** + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO: Break down process mapping into smaller, focused methods + */ public static function processToThinnedProcess(Process $myProcess): ThinnedProcess { if (!$myProcess || !isset($myProcess->id)) { @@ -183,7 +193,7 @@ public static function thinnedProcessToProcess(ThinnedProcess $thinnedProcess): $processEntity->id = $thinnedProcess->processId; $processEntity->authKey = $thinnedProcess->authKey ?? null; $processEntity->customTextfield = $thinnedProcess->customTextfield ?? null; -// Moved to Process level + // Moved to Process level $client = new Client(); $client->familyName = $thinnedProcess->familyName ?? null; @@ -258,6 +268,6 @@ public static function contactToThinnedContact($contact): ThinnedContact */ public static function providerToThinnedProvider(Provider $provider): ThinnedProvider { - return new ThinnedProvider(id: isset($provider->id) ? (int) $provider->id : null, name: isset($provider->name) ? $provider->name : null, source: isset($provider->source) ? $provider->source : null, lon: isset($provider->data['geo']['lon']) ? (float)$provider->data['geo']['lon'] : null, lat: isset($provider->data['geo']['lat']) ? (float)$provider->data['geo']['lat'] : null, contact: isset($provider->contact) ? self::contactToThinnedContact($provider->contact) : null,); + return new ThinnedProvider(id: isset($provider->id) ? (int) $provider->id : null, name: isset($provider->name) ? $provider->name : null, source: isset($provider->source) ? $provider->source : null, lon: isset($provider->data['geo']['lon']) ? (float) $provider->data['geo']['lon'] : null, lat: isset($provider->data['geo']['lat']) ? (float) $provider->data['geo']['lat'] : null, contact: isset($provider->contact) ? self::contactToThinnedContact($provider->contact) : null, ); } } diff --git a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ValidationService.php b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ValidationService.php index 48e3014c3..bea5a227c 100644 --- a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ValidationService.php +++ b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ValidationService.php @@ -13,6 +13,10 @@ use DateTime; use Psr\Http\Message\ServerRequestInterface; +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @TODO: Split this service into domain-specific validation services + */ class ValidationService { private static ?string $currentLanguage = null; @@ -21,7 +25,7 @@ class ValidationService private const PHONE_PATTERN = '/^\+?[1-9]\d{6,14}$/'; private const SERVICE_COUNT_PATTERN = '/^\d+$/'; private const MAX_FUTURE_DAYS = 365; -// Maximum days in the future for appointments + // Maximum days in the future for appointments public static function setLanguageContext(?string $language): void { @@ -85,6 +89,10 @@ public static function validateServiceLocationCombination(int $officeId, array $ : ['errors' => [self::getError('invalidLocationAndServiceCombination')]]; } + /** + * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO: Extract validation rules into separate rule objects using the specification pattern + */ public static function validateGetBookableFreeDays(?array $officeIds, ?array $serviceIds, ?string $startDate, ?string $endDate, ?array $serviceCounts): array { $errors = []; @@ -364,7 +372,7 @@ private static function isValidServiceCounts(?array $serviceCounts): bool } foreach ($serviceCounts as $count) { - if (!is_numeric($count) || $count < 0 || !preg_match(self::SERVICE_COUNT_PATTERN, (string)$count)) { + if (!is_numeric($count) || $count < 0 || !preg_match(self::SERVICE_COUNT_PATTERN, (string) $count)) { return false; } } diff --git a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ZmsApiFacadeService.php b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ZmsApiFacadeService.php index 8c3d0b6df..aae56d043 100644 --- a/zmscitizenapi/src/Zmscitizenapi/Services/Core/ZmsApiFacadeService.php +++ b/zmscitizenapi/src/Zmscitizenapi/Services/Core/ZmsApiFacadeService.php @@ -30,6 +30,10 @@ use BO\Zmsentities\Collection\RequestList; use BO\Zmsentities\Collection\ProcessList; +/** + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @TODO: Break down this facade into smaller domain-specific facades or use the Command pattern + */ class ZmsApiFacadeService { private static ?string $currentLanguage = null; @@ -348,7 +352,7 @@ public static function getBookableFreeDays(array $officeIds, array $serviceIds, ]; } - $freeDays = ZmsApiClientService::getFreeDays(new ProviderList($providers), new RequestList($services), $firstDay, $lastDay,) ?? new Calendar(); + $freeDays = ZmsApiClientService::getFreeDays(new ProviderList($providers), new RequestList($services), $firstDay, $lastDay, ) ?? new Calendar(); $daysCollection = $freeDays->days; $formattedDays = []; foreach ($daysCollection as $day) { @@ -432,12 +436,12 @@ private static function processFreeSlots(ProcessList $freeSlots): array if (isset($appointment->date)) { $timestamp = (int) $appointment->date; if ($timestamp > $currentTimestamp) { - $timestamps[$providerId][$timestamp] = true; + $timestamps[$providerId][$timestamp] = true; } } } } - return $timestamps; + return $timestamps; }, []); foreach ($appointmentTimestamps as $providerId => &$timestamps) { $timestamps = array_keys($timestamps); diff --git a/zmsticketprinter/src/Zmsticketprinter/Application.php b/zmsticketprinter/src/Zmsticketprinter/Application.php index 7d12f7d36..a1d865c18 100644 --- a/zmsticketprinter/src/Zmsticketprinter/Application.php +++ b/zmsticketprinter/src/Zmsticketprinter/Application.php @@ -9,6 +9,11 @@ namespace BO\Zmsticketprinter; +/** + * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @TODO: Refactor this class into smaller focused classes (LoggerInitializer, MiddlewareInitializer) to reduce complexity + */ class Application extends \BO\Slim\Application { /** @@ -26,13 +31,13 @@ class Application extends \BO\Slim\Application public static $supportedLanguages = array( // Default language 'de' => array( - 'name' => 'Deutsch', - 'locale' => 'de_DE.utf-8', + 'name' => 'Deutsch', + 'locale' => 'de_DE.utf-8', 'default' => true, ), 'en' => array( - 'name' => 'English', - 'locale' => 'en_GB.utf-8', + 'name' => 'English', + 'locale' => 'en_GB.utf-8', 'default' => false, ) );