Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.74% covered (warning)
81.74%
94 / 115
25.00% covered (danger)
25.00%
4 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
CommerceApiRouteGroup
81.58% covered (warning)
81.58%
93 / 114
25.00% covered (danger)
25.00%
4 / 16
126.06
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 route
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 routeTaxEndpoints
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 handleTaxRatesRoute
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
7.07
 handleTaxRatesIdRoute
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
6.40
 handleTaxGroupsRoute
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
7.07
 handleTaxGroupsIdRoute
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
7.33
 handleTaxRulesRoute
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
7.07
 handleTaxRulesIdRoute
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
7.33
 handleTaxCalcRoute
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 routeDiscountEndpoints
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
7.07
 handleDiscountIdRoute
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
7.33
 routeCurrencyEndpoints
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
7.05
 routeCurrencySpecialEndpoints
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
5.20
 routeCurrencyCodeEndpoints
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 routeCurrencyCodeCrud
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
5.58
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Api\Presentation\Group;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\Currencies\Presentation\Api\CurrenciesApiController;
12use App\Modules\Discount\Presentation\Api\DiscountRatesApiController;
13use App\Modules\Tax\Presentation\Api\TaxCalculateApiController;
14use App\Modules\Tax\Presentation\Api\TaxGroupsApiController;
15use App\Modules\Tax\Presentation\Api\TaxRatesApiController;
16use App\Modules\Tax\Presentation\Api\TaxRulesApiController;
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19
20/**
21 * Handles REST API routing for Tax management, rates, groups, rules, simulation, Discounts, and Currencies.
22 */
23final readonly class CommerceApiRouteGroup
24{
25    private const string PATH_TAX_RATES = '/api/v1/tax/rates';
26    private const string PATH_TAX_GROUPS = '/api/v1/tax/groups';
27    private const string PATH_TAX_RULES = '/api/v1/tax/rules';
28    private const string PATH_DISCOUNTS = '/api/v1/discounts';
29    private const string PATH_CURRENCIES = '/api/v1/currencies';
30    private const string REGEX_ID_SUFFIX = '/(\d+)$#';
31
32    public function __construct(
33        private ?TaxRatesApiController $taxRatesApi = null,
34        private ?TaxGroupsApiController $taxGroupsApi = null,
35        private ?TaxRulesApiController $taxRulesApi = null,
36        private ?TaxCalculateApiController $taxCalculateApi = null,
37        private ?DiscountRatesApiController $discountsApi = null,
38        private ?CurrenciesApiController $currenciesApi = null,
39    ) {
40    }
41
42    /**
43     * Routes commerce requests.
44     */
45    public function route(string $path, ServerRequestInterface $req): ?ResponseInterface
46    {
47        return $this->routeTaxEndpoints($path, $req)
48            ?? $this->routeDiscountEndpoints($path, $req)
49            ?? $this->routeCurrencyEndpoints($path, $req);
50    }
51
52    private function routeTaxEndpoints(string $path, ServerRequestInterface $req): ?ResponseInterface
53    {
54        return $this->handleTaxRatesRoute($path, $req)
55            ?? $this->handleTaxGroupsRoute($path, $req)
56            ?? $this->handleTaxRulesRoute($path, $req)
57            ?? $this->handleTaxCalcRoute($path, $req);
58    }
59
60    private function handleTaxRatesRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
61    {
62        if (!str_starts_with($path, self::PATH_TAX_RATES) || $this->taxRatesApi === null) {
63            return null;
64        }
65
66        if ($path === self::PATH_TAX_RATES) {
67            return match ($req->getMethod()) {
68                'GET'   => $this->taxRatesApi->list($req),
69                'POST'  => $this->taxRatesApi->create($req),
70                default => null,
71            };
72        }
73
74        return $this->handleTaxRatesIdRoute($path, $req);
75    }
76
77    private function handleTaxRatesIdRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
78    {
79        if (preg_match('#^' . preg_quote(self::PATH_TAX_RATES, '#') . self::REGEX_ID_SUFFIX, $path, $m)) {
80            $reqWithId = $req->withAttribute('id', (int) $m[1]);
81            return match ($req->getMethod()) {
82                'GET'    => $this->taxRatesApi->detail($reqWithId),
83                'PUT'    => $this->taxRatesApi->update($reqWithId),
84                'DELETE' => $this->taxRatesApi->delete($reqWithId),
85                default  => null,
86            };
87        }
88
89        return null;
90    }
91
92    private function handleTaxGroupsRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
93    {
94        if (!str_starts_with($path, self::PATH_TAX_GROUPS) || $this->taxGroupsApi === null) {
95            return null;
96        }
97
98        if ($path === self::PATH_TAX_GROUPS) {
99            return match ($req->getMethod()) {
100                'GET'   => $this->taxGroupsApi->list($req),
101                'POST'  => $this->taxGroupsApi->create($req),
102                default => null,
103            };
104        }
105
106        return $this->handleTaxGroupsIdRoute($path, $req);
107    }
108
109    private function handleTaxGroupsIdRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
110    {
111        if (preg_match('#^' . preg_quote(self::PATH_TAX_GROUPS, '#') . self::REGEX_ID_SUFFIX, $path, $m)) {
112            $reqWithId = $req->withAttribute('id', (int) $m[1]);
113            return match ($req->getMethod()) {
114                'GET'    => $this->taxGroupsApi->detail($reqWithId),
115                'PUT'    => $this->taxGroupsApi->update($reqWithId),
116                'DELETE' => $this->taxGroupsApi->delete($reqWithId),
117                default  => null,
118            };
119        }
120
121        return null;
122    }
123
124    private function handleTaxRulesRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
125    {
126        if (!str_starts_with($path, self::PATH_TAX_RULES) || $this->taxRulesApi === null) {
127            return null;
128        }
129
130        if ($path === self::PATH_TAX_RULES) {
131            return match ($req->getMethod()) {
132                'GET'   => $this->taxRulesApi->list($req),
133                'POST'  => $this->taxRulesApi->create($req),
134                default => null,
135            };
136        }
137
138        return $this->handleTaxRulesIdRoute($path, $req);
139    }
140
141    private function handleTaxRulesIdRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
142    {
143        if (preg_match('#^' . preg_quote(self::PATH_TAX_RULES, '#') . self::REGEX_ID_SUFFIX, $path, $m)) {
144            $reqWithId = $req->withAttribute('id', (int) $m[1]);
145            return match ($req->getMethod()) {
146                'GET'    => $this->taxRulesApi->detail($reqWithId),
147                'PUT'    => $this->taxRulesApi->update($reqWithId),
148                'DELETE' => $this->taxRulesApi->delete($reqWithId),
149                default  => null,
150            };
151        }
152
153        return null;
154    }
155
156    private function handleTaxCalcRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
157    {
158        if ($this->taxCalculateApi === null || $req->getMethod() !== 'POST') {
159            return null;
160        }
161
162        return match ($path) {
163            '/api/v1/tax/calculate' => $this->taxCalculateApi->calculate($req),
164            '/api/v1/tax/simulate'  => $this->taxCalculateApi->simulate($req),
165            default                 => null,
166        };
167    }
168
169    private function routeDiscountEndpoints(string $path, ServerRequestInterface $req): ?ResponseInterface
170    {
171        if (!str_starts_with($path, self::PATH_DISCOUNTS) || $this->discountsApi === null) {
172            return null;
173        }
174
175        if ($path === self::PATH_DISCOUNTS) {
176            return match ($req->getMethod()) {
177                'GET'   => $this->discountsApi->list($req),
178                'POST'  => $this->discountsApi->create($req),
179                default => null,
180            };
181        }
182
183        return $this->handleDiscountIdRoute($path, $req);
184    }
185
186    private function handleDiscountIdRoute(string $path, ServerRequestInterface $req): ?ResponseInterface
187    {
188        if (preg_match('#^' . preg_quote(self::PATH_DISCOUNTS, '#') . self::REGEX_ID_SUFFIX, $path, $m)) {
189            $reqWithId = $req->withAttribute('id', (int) $m[1]);
190            return match ($req->getMethod()) {
191                'GET'          => $this->discountsApi->detail($reqWithId),
192                'PUT', 'PATCH' => $this->discountsApi->update($reqWithId),
193                'DELETE'       => $this->discountsApi->delete($reqWithId),
194                default        => null,
195            };
196        }
197
198        return null;
199    }
200
201    private function routeCurrencyEndpoints(string $path, ServerRequestInterface $req): ?ResponseInterface
202    {
203        if (!str_starts_with($path, self::PATH_CURRENCIES) || $this->currenciesApi === null) {
204            return null;
205        }
206
207        $method = $req->getMethod();
208        if ($path === self::PATH_CURRENCIES) {
209            return match ($method) {
210                'GET'   => $this->currenciesApi->list($req),
211                'POST'  => $this->currenciesApi->create($req),
212                default => null,
213            };
214        }
215
216        return $this->routeCurrencySpecialEndpoints($path, $req, $method);
217    }
218
219    private function routeCurrencySpecialEndpoints(
220        string $path,
221        ServerRequestInterface $req,
222        string $method
223    ): ?ResponseInterface {
224        if ($path === '/api/v1/currencies/sync-nbp' && $method === 'POST') {
225            return $this->currenciesApi->syncNbp($req);
226        }
227
228        if ($path === '/api/v1/currencies/history' && $method === 'GET') {
229            return $this->currenciesApi->history($req);
230        }
231
232        return $this->routeCurrencyCodeEndpoints($path, $req, $method);
233    }
234
235    private function routeCurrencyCodeEndpoints(
236        string $path,
237        ServerRequestInterface $req,
238        string $method
239    ): ?ResponseInterface {
240        if (preg_match('#^/api/v1/currencies/([A-Za-z]{3})/rate$#', $path, $m) && $method === 'POST') {
241            return $this->currenciesApi->updateRate($req->withAttribute('code', $m[1]));
242        }
243
244        if (preg_match('#^/api/v1/currencies/([A-Za-z]{3})/history$#', $path, $m) && $method === 'GET') {
245            return $this->currenciesApi->history($req->withAttribute('code', $m[1]));
246        }
247
248        return $this->routeCurrencyCodeCrud($path, $req, $method);
249    }
250
251    private function routeCurrencyCodeCrud(
252        string $path,
253        ServerRequestInterface $req,
254        string $method
255    ): ?ResponseInterface {
256        if (preg_match('#^/api/v1/currencies/([A-Za-z]{3})$#', $path, $m)) {
257            return match ($method) {
258                'GET'    => $this->currenciesApi->detail($req->withAttribute('code', $m[1])),
259                'DELETE' => $this->currenciesApi->delete($req->withAttribute('code', $m[1])),
260                default  => null,
261            };
262        }
263
264        return null;
265    }
266}