Skip to content

Commit 2431937

Browse files
committed
feat(api): add generic query parameter type casting denormalizer
HTTP query parameters are always strings, but CQRS query constructors expect properly typed values (int, bool, float, etc.). This denormalizer uses reflection to automatically cast query parameter values to their expected types based on the CQRS query constructor signature. Benefits: - Works for ALL CQRS queries (not just SearchProducts) - No need for query-specific denormalizers - Handles int, float, bool, and string types - Fails gracefully if reflection is not possible This is a cleaner, more maintainable solution than having separate denormalizers for each CQRS query that uses query parameters.
1 parent e455967 commit 2431937

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

config/admin/services.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,12 @@ services:
2828
PrestaShop\Module\APIResources\Validation\IframeValidationGroupsResolver:
2929
arguments:
3030
- '@prestashop.adapter.legacy.configuration'
31+
32+
# Generic denormalizer that handles type casting for query parameters in CQRS queries
33+
# This is needed because HTTP query parameters are always strings, but CQRS queries
34+
# expect properly typed parameters (int, bool, etc.)
35+
PrestaShop\Module\APIResources\Normalizer\QueryParameterTypeCastDenormalizer:
36+
autowire: true
37+
autoconfigure: true
38+
tags:
39+
- { name: 'serializer.normalizer', priority: 10 }
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
/**
3+
* Copyright since 2007 PrestaShop SA and Contributors
4+
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
5+
*
6+
* NOTICE OF LICENSE
7+
*
8+
* This source file is subject to the Academic Free License version 3.0
9+
* that is bundled with this package in the file LICENSE.md.
10+
* It is also available through the world-wide-web at this URL:
11+
* https://opensource.org/licenses/AFL-3.0
12+
* If you did not receive a copy of the license and are unable to
13+
* obtain it through the world-wide-web, please send an email
14+
* to license@prestashop.com so we can send you a copy immediately.
15+
*
16+
* @author PrestaShop SA and Contributors <contact@prestashop.com>
17+
* @copyright Since 2007 PrestaShop SA and Contributors
18+
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace PrestaShop\Module\APIResources\Normalizer;
24+
25+
use PrestaShopBundle\ApiPlatform\Normalizer\CQRSApiNormalizer;
26+
use ReflectionClass;
27+
use ReflectionException;
28+
use ReflectionNamedType;
29+
30+
/**
31+
* Generic denormalizer that casts query parameter string values to their expected types
32+
* for ALL CQRS queries. This is necessary because HTTP query parameters are always strings,
33+
* but CQRS query constructors expect properly typed values (int, bool, float, etc.).
34+
*
35+
* This provides a generic solution that works for any CQRS query without needing
36+
* query-specific denormalizers.
37+
*/
38+
class QueryParameterTypeCastDenormalizer extends CQRSApiNormalizer
39+
{
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
44+
{
45+
// Only process if data is an array (query parameters)
46+
if (is_array($data)) {
47+
$data = $this->castQueryParametersToExpectedTypes($data, $type);
48+
}
49+
50+
return parent::denormalize($data, $type, $format, $context);
51+
}
52+
53+
/**
54+
* Cast string query parameter values to their expected types based on the CQRS query constructor.
55+
*
56+
* @param array $data The query parameters (all strings from URL)
57+
* @param string $queryClass The CQRS query class name
58+
*
59+
* @return array The data with properly typed values
60+
*/
61+
private function castQueryParametersToExpectedTypes(array $data, string $queryClass): array
62+
{
63+
try {
64+
$reflection = new ReflectionClass($queryClass);
65+
$constructor = $reflection->getConstructor();
66+
67+
if (!$constructor) {
68+
return $data;
69+
}
70+
71+
foreach ($constructor->getParameters() as $parameter) {
72+
$paramName = $parameter->getName();
73+
74+
// Skip if parameter is not in data or is not a string
75+
if (!array_key_exists($paramName, $data) || !is_string($data[$paramName])) {
76+
continue;
77+
}
78+
79+
$type = $parameter->getType();
80+
81+
// Only handle simple named types (not union, intersection, or class types)
82+
if (!$type instanceof ReflectionNamedType || !$type->isBuiltin()) {
83+
continue;
84+
}
85+
86+
// Cast the string value to the expected type
87+
$data[$paramName] = $this->castValue($data[$paramName], $type->getName());
88+
}
89+
} catch (ReflectionException $e) {
90+
// If reflection fails, return data unchanged
91+
return $data;
92+
}
93+
94+
return $data;
95+
}
96+
97+
/**
98+
* Cast a string value to the specified PHP type.
99+
*
100+
* @param string $value The string value from query parameter
101+
* @param string $typeName The target PHP type (int, float, bool, string)
102+
*
103+
* @return mixed The value cast to the appropriate type
104+
*/
105+
private function castValue(string $value, string $typeName): mixed
106+
{
107+
return match ($typeName) {
108+
'int' => (int) $value,
109+
'float' => (float) $value,
110+
'bool' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
111+
'string' => $value,
112+
default => $value,
113+
};
114+
}
115+
116+
/**
117+
* {@inheritdoc}
118+
*/
119+
public function getSupportedTypes(?string $format): array
120+
{
121+
// Support all CQRS queries (conventionally in Core\Domain namespace)
122+
return [
123+
'PrestaShop\PrestaShop\Core\Domain\*' => true,
124+
];
125+
}
126+
}
127+

0 commit comments

Comments
 (0)