Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.66% covered (warning)
88.66%
86 / 97
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExceptionService
88.66% covered (warning)
88.66%
86 / 97
50.00% covered (danger)
50.00%
2 / 4
36.79
0.00% covered (danger)
0.00%
0 / 1
 setLanguageContext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLanguageContext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handleException
89.36% covered (warning)
89.36%
84 / 94
0.00% covered (danger)
0.00%
0 / 1
33.23
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Core;
6
7use BO\Zmscitizenapi\Localization\ErrorMessages;
8
9class ExceptionService
10{
11    private static ?string $currentLanguage = null;
12    public static function setLanguageContext(?string $language): void
13    {
14        self::$currentLanguage = $language;
15    }
16
17    public static function getLanguageContext(): ?string
18    {
19        return self::$currentLanguage;
20    }
21
22    private static function getError(string $key): array
23    {
24        return ErrorMessages::get($key, self::$currentLanguage);
25    }
26
27    /**
28     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
29     * @TODO: Consider using a strategy pattern or error handler chain to reduce method complexity
30     */
31    public static function handleException(\Exception $e): never
32    {
33        $exceptionName = json_decode(json_encode($e), true)['template'] ?? null;
34        $error = null;
35
36        switch ($exceptionName) {
37            // Zmsslim exception
38            case 'Slim\\Exception\\HttpNotFoundException':
39                $error = self::getError('notFound');
40
41                break;
42
43            // ZmsClient exception
44            case 'BO\\Zmsclient\\Exception':
45                $error = self::getError('zmsClientCommunicationError');
46
47                break;
48            // Missing mail template exceptions
49            case 'Twig\\Error\\RuntimeError':
50                $error = self::getError('mailNotFound');
51
52                break;
53            case 'Twig\\Error\\LoaderError':
54                $error = self::getError('mailNotFound');
55
56                break;
57            case 'BO\\Zmsapi\\Exception\\Mail\\MailNotFound':
58                $error = self::getError('mailNotFound');
59
60                break;
61            // Process exceptions
62            case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotFound':
63                $error = self::getError('appointmentNotFound');
64
65                break;
66            case 'BO\\Zmsapi\\Exception\\Process\\AuthKeyMatchFailed':
67                $error = self::getError('authKeyMismatch');
68
69                break;
70            case 'BO\\Zmsapi\\Exception\\Process\\ProcessAlreadyCalled':
71                $error = self::getError('processAlreadyCalled');
72
73                break;
74            case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotReservedAnymore':
75                $error = self::getError('processNotReservedAnymore');
76
77                break;
78            case 'BO\\Zmsapi\\Exception\\Process\\ProcessNotPreconfirmedAnymore':
79                $error = self::getError('processNotPreconfirmedAnymore');
80
81                break;
82            case 'BO\\Zmsapi\\Exception\\Process\\ProcessDeleteFailed':
83                $error = self::getError('processDeleteFailed');
84
85                break;
86            case 'BO\\Zmsapi\\Exception\\Process\\ProcessInvalid':
87                $error = self::getError('processInvalid');
88
89                break;
90            case 'BO\\Zmsapi\\Exception\\Process\\ProcessAlreadyExists':
91                $error = self::getError('processAlreadyExists');
92
93                break;
94            case 'BO\\Zmsapi\\Exception\\Process\\EmailRequired':
95                $error = self::getError('emailIsRequired');
96
97                break;
98            case 'BO\\Zmsapi\\Exception\\Process\\TelephoneRequired':
99                $error = self::getError('telephoneIsRequired');
100
101                break;
102            case 'BO\\Zmsapi\\Exception\\Process\\MoreThanAllowedAppointmentsPerMail':
103                $error = self::getError('tooManyAppointmentsWithSameMail');
104
105                break;
106            case 'BO\\Zmsapi\\Exception\\Process\\PreconfirmationExpired':
107                $error = self::getError('preconfirmationExpired');
108
109                break;
110            case 'BO\\Zmsapi\\Exception\\Process\\ApiclientInvalid':
111                $error = self::getError('invalidApiClient');
112
113                break;
114            // Calendar exceptions
115            case 'BO\\Zmsapi\\Exception\\Calendar\\InvalidFirstDay':
116                $error = self::getError('invalidDateRange');
117
118                break;
119            case 'BO\\Zmsapi\\Exception\\Calendar\\AppointmentsMissed':
120                $error = self::getError('noAppointmentForThisScope');
121                break;
122            case 'BO\\Zmsdb\\Exception\\CalendarWithoutScopes':
123                $error = self::getError('noAppointmentForThisScope');
124                break;
125            // Other entity exceptions
126            case 'BO\\Zmsapi\\Exception\\Department\\DepartmentNotFound':
127                $error = self::getError('departmentNotFound');
128
129                break;
130            case 'BO\\Zmsapi\\Exception\\Organisation\\OrganisationNotFound':
131                $error = self::getError('organisationNotFound');
132
133                break;
134            case 'BO\\Zmsapi\\Exception\\Provider\\ProviderNotFound':
135                $error = self::getError('providerNotFound');
136
137                break;
138            case 'BO\\Zmsapi\\Exception\\Request\\RequestNotFound':
139                $error = self::getError('requestNotFound');
140                // Fall-through intentional - same error handling for both RequestNotFound exceptions
141            case 'BO\\Zmsdb\\Exception\\Request\\RequestNotFound':
142                $error = self::getError('requestNotFound');
143
144                break;
145            case 'BO\\Zmsapi\\Exception\\Scope\\ScopeNotFound':
146                $error = self::getError('scopeNotFound');
147
148                break;
149            case 'BO\\Zmsapi\\Exception\\Source\\SourceNotFound':
150                $error = self::getError('sourceNotFound');
151
152                break;
153            case 'BO\\Zmsentities\\Exception\\SchemaValidation':
154                $error = self::getError('invalidSchema');
155
156                break;
157
158            // Use original message for unmapped exceptions
159            default:
160                $error = [
161                    'errorCode' => $exceptionName ?? 'unknown',
162                    'errorMessage' => $e->getMessage(),
163                    'statusCode' => $e->getCode() ?: 500
164                ];
165        }
166
167        throw new \RuntimeException($error['errorCode'] . ': ' . $error['errorMessage'], $error['statusCode'], $e);
168    }
169}