Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
83.96% covered (warning)
83.96%
89 / 106
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExceptionService
83.96% covered (warning)
83.96%
89 / 106
50.00% covered (danger)
50.00%
2 / 4
43.96
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
84.47% covered (warning)
84.47%
87 / 103
0.00% covered (danger)
0.00%
0 / 1
39.59
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Core;
6
7use BO\Zmscitizenapi\Utils\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\\MoreThanAllowedSlotsPerAppointment':
107                $error = self::getError('tooManySlotsPerAppointment');
108
109                break;
110            case 'BO\\Zmsapi\\Exception\\Process\\MoreThanAllowedQuantityPerService':
111                $error = self::getError('tooManyServicesPerAppointment');
112
113                break;
114            case 'BO\\Zmsapi\\Exception\\Process\\PreconfirmationExpired':
115                $error = self::getError('preconfirmationExpired');
116
117                break;
118            case 'BO\\Zmsapi\\Exception\\Process\\ApiclientInvalid':
119                $error = self::getError('invalidApiClient');
120
121                break;
122            // Calendar exceptions
123            case 'BO\\Zmsapi\\Exception\\Calendar\\InvalidFirstDay':
124                $error = self::getError('invalidDateRange');
125
126                break;
127            case 'BO\\Zmsapi\\Exception\\Calendar\\AppointmentsMissed':
128                $error = self::getError('noAppointmentForThisScope');
129                break;
130            case 'BO\\Zmsdb\\Exception\\CalendarWithoutScopes':
131                $error = self::getError('noAppointmentForThisScope');
132                break;
133            // Other entity exceptions
134            case 'BO\\Zmsapi\\Exception\\Department\\DepartmentNotFound':
135                $error = self::getError('departmentNotFound');
136
137                break;
138            case 'BO\\Zmsapi\\Exception\\Organisation\\OrganisationNotFound':
139                $error = self::getError('organisationNotFound');
140
141                break;
142            case 'BO\\Zmsapi\\Exception\\Provider\\ProviderNotFound':
143                $error = self::getError('providerNotFound');
144
145                break;
146            case 'BO\\Zmsapi\\Exception\\Request\\RequestNotFound':
147                $error = self::getError('requestNotFound');
148                // Fall-through intentional - same error handling for both RequestNotFound exceptions
149            case 'BO\\Zmsdb\\Exception\\Request\\RequestNotFound':
150                $error = self::getError('requestNotFound');
151
152                break;
153            case 'BO\\Zmsapi\\Exception\\Scope\\ScopeNotFound':
154                $error = self::getError('scopeNotFound');
155
156                break;
157            case 'BO\\Zmsapi\\Exception\\Source\\SourceNotFound':
158                $error = self::getError('sourceNotFound');
159
160                break;
161            case 'BO\\Zmsentities\\Exception\\SchemaValidation':
162                $error = self::getError('invalidSchema');
163
164                break;
165            case 'BO\\Zmsapi\\Exception\\Useraccount\\InvalidCredentials':
166                $error = self::getError('invalidCredentials');
167
168                break;
169
170            // Use original message for unmapped exceptions
171            default:
172                $error = [
173                    'errorCode' => $exceptionName ?? 'unknown',
174                    'errorMessage' => $e->getMessage(),
175                    'statusCode' => $e->getCode() ?: 500
176                ];
177        }
178
179        throw new \RuntimeException($error['errorCode'] . ': ' . $error['errorMessage'], $error['statusCode'], $e);
180    }
181}