Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.47% covered (success)
92.47%
258 / 279
50.00% covered (danger)
50.00%
6 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
MapperService
92.47% covered (success)
92.47%
258 / 279
50.00% covered (danger)
50.00%
6 / 12
161.85
0.00% covered (danger)
0.00%
0 / 1
 mapScopeForProvider
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 mapOfficesWithScope
95.35% covered (success)
95.35%
41 / 43
0.00% covered (danger)
0.00%
0 / 1
41
 mapCombinable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 mapServicesWithCombinations
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
10
 mapRelations
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 scopeToThinnedScope
91.43% covered (success)
91.43%
32 / 35
0.00% covered (danger)
0.00%
0 / 1
25.39
 processToThinnedProcess
81.40% covered (warning)
81.40%
35 / 43
0.00% covered (danger)
0.00%
0 / 1
45.82
 thinnedProcessToProcess
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
4
 createScope
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
1 / 1
6
 createRequests
66.67% covered (warning)
66.67%
12 / 18
0.00% covered (danger)
0.00%
0 / 1
4.59
 contactToThinnedContact
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 providerToThinnedProvider
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
8
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi\Services\Core;
6
7use BO\Zmscitizenapi\Helper\ClientIpHelper;
8use BO\Zmscitizenapi\Models\Office;
9use BO\Zmscitizenapi\Models\Combinable;
10use BO\Zmscitizenapi\Models\OfficeServiceRelation;
11use BO\Zmscitizenapi\Models\Service;
12use BO\Zmscitizenapi\Models\ThinnedContact;
13use BO\Zmscitizenapi\Models\ThinnedProcess;
14use BO\Zmscitizenapi\Models\ThinnedProvider;
15use BO\Zmscitizenapi\Models\ThinnedScope;
16use BO\Zmscitizenapi\Models\Collections\OfficeList;
17use BO\Zmscitizenapi\Models\Collections\OfficeServiceRelationList;
18use BO\Zmscitizenapi\Models\Collections\ServiceList;
19use BO\Zmscitizenapi\Models\Collections\ThinnedScopeList;
20use BO\Zmsentities\Appointment;
21use BO\Zmsentities\Client;
22use BO\Zmsentities\Contact;
23use BO\Zmsentities\Process;
24use BO\Zmsentities\Provider;
25use BO\Zmsentities\Request;
26use BO\Zmsentities\Scope;
27use BO\Zmsentities\Collection\ProviderList;
28use BO\Zmsentities\Collection\RequestList;
29use BO\Zmsentities\Collection\RequestRelationList;
30
31/**
32 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
33 * @TODO: Extract class has ExcessiveClassComplexity 101 vs 100
34 */
35class MapperService
36{
37    public static function mapScopeForProvider(int $providerId, ?ThinnedScopeList $scopes): ThinnedScope
38    {
39        if (!$scopes) {
40            return new ThinnedScope();
41        }
42
43        $matchingScope = new ThinnedScope();
44        foreach ($scopes->getScopes() as $scope) {
45            if ($scope->provider && $scope->provider->id === $providerId) {
46                $matchingScope = $scope;
47                break;
48            }
49        }
50
51        return $matchingScope;
52    }
53
54    /**
55     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
56     * @SuppressWarnings(PHPMD.NPathComplexity)
57     * @TODO: Extract mapping logic into specialized mapper classes for each entity type
58     */
59    public static function mapOfficesWithScope(ProviderList $providerList, bool $showUnpublished = false): OfficeList
60    {
61        $offices = [];
62        $scopes = ZmsApiFacadeService::getScopes();
63        if (!$scopes instanceof ThinnedScopeList) {
64            return new OfficeList();
65        }
66
67        foreach ($providerList as $provider) {
68            $providerScope = self::mapScopeForProvider((int) $provider->id, $scopes);
69
70            if (!$showUnpublished && isset($provider->data['public']) && !(bool) $provider->data['public']) {
71                continue;
72            }
73
74            $offices[] = new Office(
75                id: isset($provider->id) ? (int) $provider->id : 0,
76                name: isset($provider->displayName) ? $provider->displayName : (isset($provider->name) ? $provider->name : null),
77                address: isset($provider->data['address']) ? $provider->data['address'] : null,
78                showAlternativeLocations: isset($provider->data['showAlternativeLocations']) ? $provider->data['showAlternativeLocations'] : null,
79                displayNameAlternatives: $provider->data['displayNameAlternatives'] ?? [],
80                organization: $provider->data['organization'] ?? null,
81                organizationUnit: $provider->data['organizationUnit'] ?? null,
82                slotTimeInMinutes: $provider->data['slotTimeInMinutes'] ?? null,
83                geo: isset($provider->data['geo']) ? $provider->data['geo'] : null,
84                disabledByServices: isset($provider->data['dontShowByServices']) ? $provider->data['dontShowByServices'] : [],
85                priority: isset($provider->data['prio']) ? $provider->data['prio'] : 1,
86                scope: isset($providerScope) && !isset($providerScope['errors']) ? new ThinnedScope(
87                    id: isset($providerScope->id) ? (int) $providerScope->id : 0,
88                    provider: isset($providerScope->provider) ? $providerScope->provider : null,
89                    shortName: isset($providerScope->shortName) ? (string) $providerScope->shortName : null,
90                    emailFrom: isset($providerScope->emailFrom) ? (string) $providerScope->emailFrom : null,
91                    emailRequired: isset($providerScope->emailRequired) ? (bool) $providerScope->emailRequired : null,
92                    telephoneActivated: isset($providerScope->telephoneActivated) ? (bool) $providerScope->telephoneActivated : null,
93                    telephoneRequired: isset($providerScope->telephoneRequired) ? (bool) $providerScope->telephoneRequired : null,
94                    customTextfieldActivated: isset($providerScope->customTextfieldActivated) ? (bool) $providerScope->customTextfieldActivated : null,
95                    customTextfieldRequired: isset($providerScope->customTextfieldRequired) ? (bool) $providerScope->customTextfieldRequired : null,
96                    customTextfieldLabel: isset($providerScope->customTextfieldLabel) ? (string) $providerScope->customTextfieldLabel : null,
97                    customTextfield2Activated: isset($providerScope->customTextfield2Activated) ? (bool) $providerScope->customTextfield2Activated : null,
98                    customTextfield2Required: isset($providerScope->customTextfield2Required) ? (bool) $providerScope->customTextfield2Required : null,
99                    customTextfield2Label: isset($providerScope->customTextfield2Label) ? (string) $providerScope->customTextfield2Label : null,
100                    captchaActivatedRequired: isset($providerScope->captchaActivatedRequired) ? (bool) $providerScope->captchaActivatedRequired : null,
101                    displayInfo: isset($providerScope->displayInfo) ? (string) $providerScope->displayInfo : null,
102                    slotsPerAppointment: isset($providerScope->slotsPerAppointment) ? ((string) $providerScope->slotsPerAppointment === '' ? null : (string) $providerScope->slotsPerAppointment) : null,
103                    appointmentsPerMail: isset($providerScope->appointmentsPerMail) ? ((string) $providerScope->appointmentsPerMail === '' ? null : (string) $providerScope->appointmentsPerMail) : null,
104                    whitelistedMails: isset($providerScope->whitelistedMails) ? ((string) $providerScope->whitelistedMails === '' ? null : (string) $providerScope->whitelistedMails) : null
105                ) : null,
106                maxSlotsPerAppointment: isset($providerScope) && !isset($providerScope['errors']) && isset($providerScope->slotsPerAppointment) ? ((string) $providerScope->slotsPerAppointment === '' ? null : (string) $providerScope->slotsPerAppointment) : null
107            );
108        }
109
110        return new OfficeList($offices);
111    }
112
113    public static function mapCombinable(array $serviceCombinations): ?Combinable
114    {
115        return !empty($serviceCombinations) ? new Combinable($serviceCombinations) : null;
116    }
117
118    /**
119     * Map services with combinations based on request and relation lists.
120     *
121     * @param RequestList $requestList
122     * @param RequestRelationList $relationList
123     * @return ServiceList
124     */
125    public static function mapServicesWithCombinations(
126        RequestList $requestList,
127        RequestRelationList $relationList,
128        bool $showUnpublished = false
129    ): ServiceList {
130        /** @var array<string, array<int>> $servicesProviderIds */
131        $servicesProviderIds = [];
132        foreach ($relationList as $relation) {
133            if (!$showUnpublished && !$relation->isPublic()) {
134                continue;
135            }
136
137            $serviceId = $relation->request->id;
138            $servicesProviderIds[$serviceId] ??= [];
139            $servicesProviderIds[$serviceId][] = $relation->provider->id;
140        }
141
142        /** @var Service[] $services */
143        $services = [];
144        $requestArray = iterator_to_array($requestList);
145        usort($requestArray, function ($a, $b) {
146
147            return $a->getId() <=> $b->getId();
148            // Sorting by service ID (ascending order)
149        });
150        foreach ($requestArray as $service) {
151            if (
152                !$showUnpublished
153                && isset($service->getAdditionalData()['public'])
154                && !$service->getAdditionalData()['public']
155            ) {
156                continue;
157            }
158
159            /** @var array<string, array<int>> $serviceCombinations */
160            $serviceCombinations = [];
161            $combinableData = $service->getAdditionalData()['combinable'] ?? [];
162            foreach ($combinableData as $combinationServiceId) {
163                $commonProviders = array_intersect($servicesProviderIds[$service->getId()] ?? [], $servicesProviderIds[$combinationServiceId] ?? []);
164                $serviceCombinations[$combinationServiceId] = !empty($commonProviders) ? array_values($commonProviders) : [];
165            }
166
167            $combinable = self::mapCombinable($serviceCombinations);
168            $services[] = new Service(id: (int) $service->getId(), name: $service->getName(), maxQuantity: $service->getAdditionalData()['maxQuantity'] ?? 1, combinable: $combinable ?? new Combinable());
169        }
170
171        return new ServiceList($services);
172    }
173
174
175    public static function mapRelations(
176        RequestRelationList $relationList,
177        bool $showUnpublished = false
178    ): OfficeServiceRelationList {
179        $relations = [];
180        foreach ($relationList as $relation) {
181            if (!$showUnpublished && !$relation->isPublic()) {
182                continue;
183            }
184
185            $relations[] = new OfficeServiceRelation(
186                officeId: (int) $relation->provider->id,
187                serviceId: (int) $relation->request->id,
188                slots: intval($relation->slots),
189                public: (bool) $relation->isPublic(),
190                maxQuantity: (int) $relation->maxQuantity
191            );
192        }
193
194        return new OfficeServiceRelationList($relations);
195    }
196
197    /**
198     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
199     * @SuppressWarnings(PHPMD.NPathComplexity)
200     */
201    public static function scopeToThinnedScope(Scope $scope): ThinnedScope
202    {
203        if (!$scope || !isset($scope->id)) {
204            return new ThinnedScope();
205        }
206
207        $thinnedProvider = null;
208        try {
209            if (isset($scope->provider)) {
210                $provider = $scope->provider;
211                $contact = $provider->contact ?? null;
212                $thinnedProvider = new ThinnedProvider(
213                    id: isset($provider->id) ? (int)$provider->id : null,
214                    name: $provider->name ?? null,
215                    displayName: $provider->displayName ?? null,
216                    source: $provider->source ?? null,
217                    contact: $contact ? self::contactToThinnedContact($contact) : null
218                );
219            }
220        } catch (\BO\Zmsentities\Exception\ScopeMissingProvider $e) {
221            $thinnedProvider = null;
222        }
223
224        return new ThinnedScope(
225            id: (int) ($scope->id ?? 0),
226            provider: $thinnedProvider,
227            shortName: isset($scope->shortName) ? (string) $scope->shortName : null,
228            emailFrom: (string) $scope->getEmailFrom() ?? null,
229            emailRequired: isset($scope->data['emailRequired']) ? (bool) $scope->data['emailRequired'] : null,
230            telephoneActivated: isset($scope->data['telephoneActivated']) ? (bool) $scope->data['telephoneActivated'] : null,
231            telephoneRequired: isset($scope->data['telephoneRequired']) ? (bool) $scope->data['telephoneRequired'] : null,
232            customTextfieldActivated: isset($scope->data['customTextfieldActivated']) ? (bool) $scope->data['customTextfieldActivated'] : null,
233            customTextfieldRequired: isset($scope->data['customTextfieldRequired']) ? (bool) $scope->data['customTextfieldRequired'] : null,
234            customTextfieldLabel: isset($scope->data['customTextfieldLabel']) ? (string) $scope->data['customTextfieldLabel'] : null,
235            customTextfield2Activated: isset($scope->data['customTextfield2Activated']) ? (bool) $scope->data['customTextfield2Activated'] : null,
236            customTextfield2Required: isset($scope->data['customTextfield2Required']) ? (bool) $scope->data['customTextfield2Required'] : null,
237            customTextfield2Label: isset($scope->data['customTextfield2Label']) ? (string) $scope->data['customTextfield2Label'] : null,
238            captchaActivatedRequired: isset($scope->data['captchaActivatedRequired']) ? (bool) $scope->data['captchaActivatedRequired'] : null,
239            displayInfo: isset($scope->data['displayInfo']) ? (string) $scope->data['displayInfo'] : null,
240            slotsPerAppointment: isset($scope->data['slotsPerAppointment']) ? ((string) $scope->data['slotsPerAppointment'] === '' ? null : (string) $scope->data['slotsPerAppointment']) : null,
241            appointmentsPerMail: isset($scope->data['appointmentsPerMail']) ? ((string) $scope->data['appointmentsPerMail'] === '' ? null : (string) $scope->data['appointmentsPerMail']) : null,
242            whitelistedMails: isset($scope->data['whitelistedMails']) ? ((string) $scope->data['whitelistedMails'] === '' ? null : (string) $scope->data['whitelistedMails']) : null
243        );
244    }
245
246    /**
247     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
248     * @SuppressWarnings(PHPMD.NPathComplexity)
249     * @TODO: Break down process mapping into smaller, focused methods
250     */
251    public static function processToThinnedProcess(Process $myProcess): ThinnedProcess
252    {
253        if (!$myProcess || !isset($myProcess->id)) {
254            return new ThinnedProcess();
255        }
256
257        $subRequestCounts = [];
258        $mainServiceId = null;
259        $mainServiceName = null;
260        $mainServiceCount = 0;
261        $requests = $myProcess->getRequests() ?? [];
262        if ($requests) {
263            $requests = is_array($requests) ? $requests : iterator_to_array($requests);
264            if (count($requests) > 0) {
265                $mainServiceId = $requests[0]->id;
266                foreach ($requests as $request) {
267                    if ($request->id === $mainServiceId) {
268                        $mainServiceCount++;
269                        if (!$mainServiceName && isset($request->name)) {
270                            $mainServiceName = $request->name;
271                        }
272                    } else {
273                        if (!isset($subRequestCounts[$request->id])) {
274                            $subRequestCounts[$request->id] = [
275                                'id' => (int) $request->id,
276                                'name'  => $request->name,
277                                'count' => 0,
278                            ];
279                        }
280                        $subRequestCounts[$request->id]['count']++;
281                    }
282                }
283            }
284        }
285
286        return new ThinnedProcess(
287            processId: isset($myProcess->id) ? (int) $myProcess->id : 0,
288            timestamp: (isset($myProcess->appointments[0]) && isset($myProcess->appointments[0]->date)) ? strval($myProcess->appointments[0]->date) : null,
289            authKey: isset($myProcess->authKey) ? $myProcess->authKey : null,
290            captchaToken: isset($myProcess->captchaToken) ? $myProcess->captchaToken : null,
291            familyName: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->familyName)) ? $myProcess->clients[0]->familyName : null,
292            customTextfield: isset($myProcess->customTextfield) ? $myProcess->customTextfield : null,
293            customTextfield2: isset($myProcess->customTextfield2) ? $myProcess->customTextfield2 : null,
294            email: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->email)) ? $myProcess->clients[0]->email : null,
295            telephone: (isset($myProcess->clients[0]) && isset($myProcess->clients[0]->telephone)) ? $myProcess->clients[0]->telephone : null,
296            officeName: (isset($myProcess->scope->contact) && isset($myProcess->scope->contact->name)) ? $myProcess->scope->contact->name : null,
297            officeId: (isset($myProcess->scope->provider) && isset($myProcess->scope->provider->id)) ? (int) $myProcess->scope->provider->id : 0,
298            scope: isset($myProcess->scope) ? self::scopeToThinnedScope($myProcess->scope) : null,
299            subRequestCounts: isset($subRequestCounts) ? array_values($subRequestCounts) : [],
300            serviceId: isset($mainServiceId) ? (int) $mainServiceId : 0,
301            serviceName: isset($mainServiceName) ? $mainServiceName : null,
302            serviceCount: isset($mainServiceCount) ? $mainServiceCount : 0,
303            status: (isset($myProcess->queue) && isset($myProcess->queue->status)) ? $myProcess->queue->status : null,
304            slotCount: (isset($myProcess->appointments[0]) && isset($myProcess->appointments[0]->slotCount)) ? (int) $myProcess->appointments[0]->slotCount : null
305        );
306    }
307
308    public static function thinnedProcessToProcess(ThinnedProcess $thinnedProcess): Process
309    {
310        if (!$thinnedProcess || !isset($thinnedProcess->processId)) {
311            return new Process();
312        }
313
314        $processEntity = new Process();
315        $processEntity->id = $thinnedProcess->processId;
316        $processEntity->authKey = $thinnedProcess->authKey ?? null;
317        $processEntity->customTextfield = $thinnedProcess->customTextfield ?? null;
318        $processEntity->customTextfield2 = $thinnedProcess->customTextfield2 ?? null;
319        $processEntity->captchaToken = $thinnedProcess->captchaToken ?? null;
320
321        $client = new Client();
322        $client->familyName = $thinnedProcess->familyName ?? null;
323        $client->email = $thinnedProcess->email ?? null;
324        $client->telephone = $thinnedProcess->telephone ?? null;
325        $processEntity->clients = [$client];
326
327        $appointment = new Appointment();
328        $appointment->slotCount = $thinnedProcess->slotCount ?? null;
329        $appointment->date = $thinnedProcess->timestamp ?? null;
330        $processEntity->appointments = [$appointment];
331        $processEntity->scope = self::createScope($thinnedProcess);
332        $processEntity->requests = self::createRequests($thinnedProcess);
333
334        if (isset($thinnedProcess->status)) {
335            $processEntity->queue = new \stdClass();
336            $processEntity->queue->status = $thinnedProcess->status;
337            $processEntity->status = $thinnedProcess->status;
338        }
339
340        $processEntity->lastChange = time();
341        $processEntity->createIP = ClientIpHelper::getClientIp();
342        $processEntity->createTimestamp = time();
343        return $processEntity;
344    }
345
346    private static function createScope(ThinnedProcess $thinnedProcess): Scope
347    {
348        $scope = new Scope();
349        if ($thinnedProcess->scope) {
350            $scope->id = $thinnedProcess->scope->id;
351            $scope->source = \App::$source_name;
352
353            // Set preferences as array structure
354            $scope->preferences = [
355                'client' => [
356                    'appointmentsPerMail' => $thinnedProcess->scope->getAppointmentsPerMail() ?? null,
357                    "whitelistedMails" => $thinnedProcess->scope->getWhitelistedMails() ?? null,
358                    'emailFrom' => $thinnedProcess->scope->getEmailFrom() ?? null,
359                    'emailRequired' => $thinnedProcess->scope->getEmailRequired() ?? false,
360                    'telephoneActivated' => $thinnedProcess->scope->getTelephoneActivated() ?? false,
361                    'telephoneRequired' => $thinnedProcess->scope->getTelephoneRequired() ?? false,
362                    'customTextfieldActivated' => $thinnedProcess->scope->getCustomTextfieldActivated() ?? false,
363                    'customTextfieldRequired' => $thinnedProcess->scope->getCustomTextfieldRequired() ?? false,
364                    'customTextfieldLabel' => $thinnedProcess->scope->getCustomTextfieldLabel() ?? null,
365                    'customTextfield2Activated' => $thinnedProcess->scope->getCustomTextfield2Activated() ?? false,
366                    'customTextfield2Required' => $thinnedProcess->scope->getCustomTextfield2Required() ?? false,
367                    'customTextfield2Label' => $thinnedProcess->scope->getCustomTextfield2Label() ?? null
368                ],
369                'notifications' => [
370                    'enabled' => true
371                ]
372            ];
373        }
374        if (isset($thinnedProcess->officeName)) {
375            $scope->contact = new Contact();
376            $scope->contact->name = $thinnedProcess->officeName;
377        }
378        if (isset($thinnedProcess->officeId)) {
379            $scope->provider = new Provider();
380            $scope->provider->id = $thinnedProcess->officeId;
381            if (isset($thinnedProcess->scope->provider)) {
382                $provider = $thinnedProcess->scope->provider;
383                $scope->provider->name  = $provider->name ?? null;
384                $scope->provider->displayName = $provider->displayName ?? null;
385
386                if (isset($provider->contact)) {
387                    $scope->provider->contact = new Contact();
388                    $scope->provider->contact->street = $provider->contact->street ?? null;
389                    $scope->provider->contact->streetNumber = $provider->contact->streetNumber ?? null;
390                }
391            }
392            $scope->provider->source = \App::$source_name;
393        }
394
395        return $scope;
396    }
397
398    private static function createRequests(ThinnedProcess $thinnedProcess): array
399    {
400        $requests = [];
401        $mainServiceId = $thinnedProcess->serviceId ?? null;
402        $mainServiceName = $thinnedProcess->serviceName ?? null;
403        $mainServiceCount = $thinnedProcess->serviceCount ?? 0;
404
405        for ($i = 0; $i < $mainServiceCount; $i++) {
406            $request = new Request();
407            $request->id = $mainServiceId;
408            $request->name = $mainServiceName;
409            $request->source = \App::$source_name;
410            $requests[] = $request;
411        }
412
413        foreach ($thinnedProcess->subRequestCounts ?? [] as $subRequest) {
414            for ($i = 0; $i < ($subRequest['count'] ?? 0); $i++) {
415                $request = new Request();
416                $request->id = $subRequest['id'];
417                $request->name = $subRequest['name'];
418                $request->source = \App::$source_name;
419                $requests[] = $request;
420            }
421        }
422
423        return $requests;
424    }
425
426    /**
427     * Converts a raw or existing contact object/array into a ThinnedContact model.
428     *
429     * @param object|array $contact
430     * @return ThinnedContact
431     */
432    public static function contactToThinnedContact($contact): ThinnedContact
433    {
434        if (is_array($contact)) {
435            return new ThinnedContact(
436                city: $contact['city'] ?? null,
437                country: $contact['country'] ?? null,
438                name: $contact['name'] ?? null,
439                postalCode: isset($contact['postalCode']) ? (is_null($contact['postalCode']) ? null : (string)$contact['postalCode']) : null,
440                region: $contact['region'] ?? null,
441                street: $contact['street'] ?? null,
442                streetNumber: $contact['streetNumber'] ?? null
443            );
444        }
445
446        return new ThinnedContact(
447            city: $contact->city ?? null,
448            country: $contact->country ?? null,
449            name: $contact->name ?? null,
450            postalCode: isset($contact->postalCode) ? (is_null($contact->postalCode) ? null : (string)$contact->postalCode) : null,
451            region: $contact->region ?? null,
452            street: $contact->street ?? null,
453            streetNumber: $contact->streetNumber ?? null
454        );
455    }
456
457    /**
458     * Convert a Provider object to a ThinnedProvider.
459     *
460     * @param Provider $provider
461     * @return ThinnedProvider
462     */
463    public static function providerToThinnedProvider(Provider $provider): ThinnedProvider
464    {
465        return new ThinnedProvider(
466            id: isset($provider->id) ? (int) $provider->id : null,
467            name: isset($provider->name) ? $provider->name : null,
468            displayName: isset($provider->displayName) ? $provider->displayName : null,
469            source: isset($provider->source) ? $provider->source : null,
470            lon: isset($provider->data['geo']['lon']) ? (float) $provider->data['geo']['lon'] : null,
471            lat: isset($provider->data['geo']['lat']) ? (float) $provider->data['geo']['lat'] : null,
472            contact: isset($provider->contact) ? self::contactToThinnedContact($provider->contact) : null
473        );
474    }
475}