Skip to content

Commit 5c1bb81

Browse files
committed
add castFn
1 parent 0652118 commit 5c1bb81

File tree

5 files changed

+103
-18
lines changed

5 files changed

+103
-18
lines changed

src/Metadata/Parameter.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ abstract class Parameter
2727
* @param (array<string, mixed>&array{type?: string, default?: string})|null $schema
2828
* @param array<string, mixed> $extraProperties
2929
* @param ParameterProviderInterface|callable|string|null $provider
30-
* @param list<string> $properties a list of properties this parameter applies to (works with the :property placeholder)
30+
* @param list<string> $properties a list of properties this parameter applies to (works with the :property placeholder)
3131
* @param FilterInterface|string|null $filter
32-
* @param mixed $constraints an array of Symfony constraints, or an array of Laravel rules
33-
* @param Type $nativeType the PHP native type, we cast values to an array if its a CollectionType, if not and it's an array with a single value we use it (eg: HTTP Header)
32+
* @param mixed $constraints an array of Symfony constraints, or an array of Laravel rules
33+
* @param Type $nativeType the PHP native type, we cast values to an array if its a CollectionType, if not and it's an array with a single value we use it (eg: HTTP Header)
34+
* @param ?bool $castToNativeType whether API Platform should cast your parameter to the nativeType declared
35+
* @param ?callable $castFn the closure used to cast your parameter, this gets called only when $castToNativeType is set
3436
*/
3537
public function __construct(
3638
protected ?string $key = null,
@@ -52,6 +54,7 @@ public function __construct(
5254
protected ?Type $nativeType = null,
5355
protected ?bool $castToArray = null,
5456
protected ?bool $castToNativeType = null,
57+
protected ?callable $castFn = null,
5558
) {
5659
}
5760

@@ -346,4 +349,17 @@ public function withCastToNativeType(bool $castToNativeType): self
346349

347350
return $self;
348351
}
352+
353+
public function getCastFn(): ?callable
354+
{
355+
return $this->castFn;
356+
}
357+
358+
public function withCastFn(callable $castFn): self
359+
{
360+
$self = clone $this;
361+
$self->castFn = $castFn;
362+
363+
return $self;
364+
}
349365
}

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
2929
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
3030
use ApiPlatform\Serializer\Filter\FilterInterface as SerializerFilterInterface;
31+
use ApiPlatform\State\Parameter\ValueCaster;
3132
use ApiPlatform\State\Util\StateOptionsTrait;
3233
use Psr\Container\ContainerInterface;
3334
use Psr\Log\LoggerInterface;
3435
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
3536
use Symfony\Component\TypeInfo\Type;
37+
use Symfony\Component\TypeInfo\TypeIdentifier;
3638

3739
/**
3840
* Prepares Parameters documentation by reading its filter details and declaring an OpenApi parameter.
@@ -167,6 +169,18 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
167169
}
168170
}
169171

172+
if ($parameter->getCastToNativeType() && null === $parameter->getCastFn() && ($nativeType = $parameter->getNativeType())) {
173+
if ($nativeType->isIdentifiedBy(TypeIdentifier::BOOL)) {
174+
$parameter = $parameter->withCastFn([ValueCaster::class, 'toBool']);
175+
}
176+
if ($nativeType->isIdentifiedBy(TypeIdentifier::INT)) {
177+
$parameter = $parameter->withCastFn([ValueCaster::class, 'toInt']);
178+
}
179+
if ($nativeType->isIdentifiedBy(TypeIdentifier::FLOAT)) {
180+
$parameter = $parameter->withCastFn([ValueCaster::class, 'toFloat']);
181+
}
182+
}
183+
170184
$priority = $parameter->getPriority() ?? $internalPriority--;
171185
$parameters->add($key, $parameter->withPriority($priority));
172186
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\State\Parameter;
15+
16+
/**
17+
* Caster returns the default value when a value can not be casted
18+
* This is used by parameters before they get validated by constraints
19+
* Therefore we do not need to throw exceptions, validation will just fail.
20+
*
21+
* @internal
22+
*/
23+
final class ValueCaster
24+
{
25+
public static function toBool(mixed $v): mixed
26+
{
27+
if (!\is_string($v)) {
28+
return $v;
29+
}
30+
31+
return match (strtolower($v)) {
32+
'1', 'true' => true,
33+
'0', 'false' => false,
34+
default => $v,
35+
};
36+
}
37+
38+
public static function toInt(mixed $v): mixed
39+
{
40+
if (\is_int($v)) {
41+
return $v;
42+
}
43+
44+
$value = filter_var($v, \FILTER_VALIDATE_INT);
45+
46+
return false === $value ? $v : $value;
47+
}
48+
49+
public static function toFloat(mixed $v): mixed
50+
{
51+
if (\is_float($v)) {
52+
return $v;
53+
}
54+
55+
$value = filter_var($v, \FILTER_VALIDATE_FLOAT);
56+
57+
return false === $value ? $v : $value;
58+
}
59+
}

src/State/Util/ParameterParserTrait.php

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818
use ApiPlatform\Metadata\Parameter;
1919
use ApiPlatform\State\ParameterNotFound;
2020
use Symfony\Component\HttpFoundation\Request;
21-
use Symfony\Component\TypeInfo\Type;
2221
use Symfony\Component\TypeInfo\Type\CollectionType;
2322
use Symfony\Component\TypeInfo\Type\UnionType;
24-
use Symfony\Component\TypeInfo\TypeIdentifier;
2523

2624
/**
2725
* @internal
@@ -81,19 +79,6 @@ private function extractParameterValues(Parameter $parameter, array $values): st
8179
return $value;
8280
}
8381

84-
// If "nativeType: new BuiltinType(TypeIdentifier::BOOL)"
85-
// or "schema: ['type' => 'bool'], castToNativeType: true"
86-
if ($parameter->getNativeType()?->isIdentifiedBy(TypeIdentifier::BOOL)
87-
|| (($parameter->getSchema()['type'] ?? null) === 'boolean' && $parameter->getCastToNativeType())
88-
) {
89-
// Note: a HeaderParameter value might be sent as an array...
90-
$value = match ($value) {
91-
'true', 1, '1', [true], ['true'], [1], ['1'] => true,
92-
'false', 0, '0', [false], ['false'], [0], ['0'] => false,
93-
default => $value,
94-
};
95-
}
96-
9782
$isCollectionType = fn ($t) => $t instanceof CollectionType;
9883
$isCollection = $parameter->getNativeType()?->isSatisfiedBy($isCollectionType) ?? false;
9984

@@ -114,6 +99,14 @@ private function extractParameterValues(Parameter $parameter, array $values): st
11499
$value = $value[0];
115100
}
116101

102+
if (true === $parameter->getCastToNativeType() && ($castFn = $parameter->getCastFn())) {
103+
if (\is_array($value)) {
104+
$value = array_map(fn ($v) => $castFn($v, $parameter), $value);
105+
} else {
106+
$value = $castFn($value, $parameter);
107+
}
108+
}
109+
117110
return $value;
118111
}
119112
}

tests/Fixtures/TestBundle/ApiResource/WithParameter.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
'maximum' => 100.0,
181181
'multipleOf' => 0.01,
182182
],
183+
castToNativeType: true
183184
),
184185
],
185186
provider: [self::class, 'noopProvider']
@@ -206,6 +207,7 @@
206207
'minimum' => 1,
207208
'maximum' => 5,
208209
],
210+
castToNativeType: true
209211
),
210212
],
211213
provider: [self::class, 'noopProvider']
@@ -221,6 +223,7 @@
221223
'maximum' => 100.0,
222224
'multipleOf' => 0.01,
223225
],
226+
castToNativeType: true
224227
),
225228
],
226229
provider: [self::class, 'noopProvider']

0 commit comments

Comments
 (0)