Skip to content

Commit b04f6a5

Browse files
committed
feat(serializer): render BCMath\Number (PHP 8.4+) as string instead of object
1 parent 325a04c commit b04f6a5

File tree

4 files changed

+144
-0
lines changed

4 files changed

+144
-0
lines changed

src/JsonSchema/Metadata/Property/Factory/SchemaPropertyMetadataFactory.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ private function getClassSchemaDefinition(?string $className, ?bool $readableLin
319319
return ['type' => 'string', 'format' => 'binary'];
320320
}
321321

322+
if (class_exists(\BcMath\Number::class) && is_a($className, \BcMath\Number::class, true)) {
323+
return ['type' => 'string', 'format' => 'string'];
324+
}
325+
322326
$isResourceClass = $this->isResourceClass($className);
323327
if (!$isResourceClass && is_a($className, \BackedEnum::class, true)) {
324328
$enumCases = array_map(static fn (\BackedEnum $enum): string|int => $enum->value, $className::cases());
@@ -509,6 +513,13 @@ private function getLegacyClassType(?string $className, bool $nullable, ?bool $r
509513
];
510514
}
511515

516+
if (class_exists(\BcMath\Number::class) && is_a($className, \BcMath\Number::class, true)) {
517+
return [
518+
'type' => 'string',
519+
'format' => 'string',
520+
];
521+
}
522+
512523
$isResourceClass = $this->isResourceClass($className);
513524
if (!$isResourceClass && is_a($className, \BackedEnum::class, true)) {
514525
$enumCases = array_map(static fn (\BackedEnum $enum): string|int => $enum->value, $className::cases());

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
use Symfony\Component\JsonStreamer\JsonStreamWriter;
6767
use Symfony\Component\ObjectMapper\ObjectMapper;
6868
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
69+
use Symfony\Component\Serializer\Normalizer\NumberNormalizer;
6970
use Symfony\Component\Uid\AbstractUid;
7071
use Symfony\Component\Validator\Validator\ValidatorInterface;
7172
use Symfony\Component\Yaml\Yaml;
@@ -267,6 +268,15 @@ private function registerCommonConfiguration(ContainerBuilder $container, array
267268
$loader->load('symfony/uid.php');
268269
}
269270

271+
// symfony/serializer:7.3 added the NumberNormalizer
272+
// symfony/framework-bundle:7.3 added the serializer.normalizer.number` service
273+
// if symfony/serializer >= 7.3 and symfony/framework-bundle < 7.3, the service is registred
274+
if (class_exists(NumberNormalizer::class) && !$container->has('serializer.normalizer.number')) {
275+
$numberNormalizerDefinition = new Definition(NumberNormalizer::class);
276+
$numberNormalizerDefinition->addTag('serializer.normalizer', ['built_in' => true, 'priority' => -915]);
277+
$container->setDefinition('serializer.normalizer.number', $numberNormalizerDefinition);
278+
}
279+
270280
$defaultContext = ['hydra_prefix' => $config['serializer']['hydra_prefix']] + ($container->hasParameter('serializer.default_context') ? $container->getParameter('serializer.default_context') : []);
271281

272282
$container->setParameter('api_platform.serializer.default_context', $defaultContext);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Tests\Fixtures\TestBundle\ApiResource;
15+
16+
use ApiPlatform\Metadata\ApiProperty;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Operation;
19+
use ApiPlatform\Metadata\Post;
20+
use BcMath\Number;
21+
22+
#[Get(
23+
provider: [self::class, 'provide'],
24+
)]
25+
#[Post]
26+
class MathNumber
27+
{
28+
#[ApiProperty(identifier: true)]
29+
public int $id;
30+
31+
#[ApiProperty(property: 'value')]
32+
public ?Number $value;
33+
34+
public static function provide(Operation $operation, array $uriVariables = [], array $context = []): self
35+
{
36+
$mathNumber = new self();
37+
$mathNumber->id = $uriVariables['id'];
38+
$mathNumber->value = new Number('300.55');
39+
40+
return $mathNumber;
41+
}
42+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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\Tests\Functional;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\MathNumber;
18+
use ApiPlatform\Tests\SetupClassResourcesTrait;
19+
use PHPUnit\Framework\Attributes\RequiresPhp;
20+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
21+
use Symfony\Component\Serializer\Normalizer\NumberNormalizer;
22+
23+
#[RequiresPhp('^8.4')]
24+
#[RequiresPhpExtension('bcmath')]
25+
final class MathNumberTest extends ApiTestCase
26+
{
27+
use SetupClassResourcesTrait;
28+
29+
protected static ?bool $alwaysBootKernel = false;
30+
31+
/**
32+
* @return class-string[]
33+
*/
34+
public static function getResources(): array
35+
{
36+
return [MathNumber::class];
37+
}
38+
39+
protected function setUp(): void
40+
{
41+
if (!class_exists(NumberNormalizer::class)) {
42+
$this->markTestSkipped('Requires BcMath/Number and symfony/serialiser >=7.3');
43+
}
44+
45+
parent::setUp();
46+
}
47+
48+
public function testGetMathNumber(): void
49+
{
50+
self::createClient()->request('GET', '/math_numbers/1', ['headers' => ['Accept' => 'application/ld+json']]);
51+
52+
$this->assertResponseIsSuccessful();
53+
$this->assertJsonEquals([
54+
'@context' => '/contexts/MathNumber',
55+
'@id' => '/math_numbers/1',
56+
'@type' => 'MathNumber',
57+
'id' => 1,
58+
'value' => '300.55',
59+
]);
60+
}
61+
62+
public function testPostMathNumber(): void
63+
{
64+
self::createClient()->request('POST', '/math_numbers', [
65+
'headers' => ['Content-Type' => 'application/ld+json'],
66+
'json' => [
67+
'id' => 2,
68+
'value' => '120.23',
69+
],
70+
]);
71+
72+
$this->assertResponseIsSuccessful();
73+
$this->assertJsonEquals([
74+
'@context' => '/contexts/MathNumber',
75+
'@id' => '/math_numbers/2',
76+
'@type' => 'MathNumber',
77+
'id' => 2,
78+
'value' => '120.23',
79+
]);
80+
}
81+
}

0 commit comments

Comments
 (0)