Description
Symfony version(s) affected
7.0.3
Description
Preface
With the recent release of Doctrine ORM 3 and DBAL 4 there has been a major change in at least one column type.
bigint now reads:
Maps and converts 8-byte integer values.
...Values retrieved from the database are always converted to PHP's
integer
type if they are within PHP's integer range
orstring
if they aren't.
Additionally, if you did not yet make the jump to ORM 3/DBAL 4 and are using ORM 2.19 which acts like Sf .4
and the
next major .0
you can use int
typehints in your PHP code with a bigint
column type if you go for a string|int
union. You can read more about it in this issue: Invalid field error reported for bigint DBAL type.
Another case would be if you know that your project's tech will always rely on int
within your PHP_INT_MAX
you could
also override the default bigint
type and bind it to int
.
This is also dependent of your DB engine too, but generally speaking, all of the above are true for pgsql. How one goes about handling this Doctrine hiccup is up to developer's choice and from my POV any of the solutions that can be used for Doctrine is fine.
Symfony
Let's assume that you are using one of the methods to have Doctrine happy but without jeopardising your project. If your project is in any capacity in need of denormalizing request data into PHP objects this is the next step that breaks.
At one point, for objects, the AbstractObjectNormalizer::getTypes()
is called, which in turn makes use of
PropertyInfoExtractor
to resolve the Type
of a bigint
property that is in some way type-hinted as int
and request data will use int
values too.
DoctrineExtractor::getTypes()
will internally try to get the PHP type of bigint
which for now is always going to map
it to string
.
// at about line ~250
return match ($doctrineType) {
Types::SMALLINT,
Types::INTEGER => Type::BUILTIN_TYPE_INT,
Types::FLOAT => Type::BUILTIN_TYPE_FLOAT,
Types::BIGINT, // <- here
Types::STRING,
Types::TEXT,
Types::GUID,
Types::DECIMAL => Type::BUILTIN_TYPE_STRING, // <- mapped to string
// ...
This causes a gate-keeping effect with the following exception message:
The type of the "bigint" attribute for class "IHaveNumbers" must be one of "string" ("int" given).
For this there are some possible ways to go about it.
A) You can use DISABLE_TYPE_ENFORCEMENT
on your context and the value is used as is, letting it be handled on code level
which may result in \Error
issues.
B) Switch your property to string
only and add more cast juggling.
C) Switch your property to string
and also reflect this in your general API so that request data is now only string
.
D) Update / Decorate DoctrineExtractor
with the following hack solution:
// new block
if (Types::BIGINT === $typeOfField) {
return [
new Type(Type::BUILTIN_TYPE_INT, $nullable),
new Type(Type::BUILTIN_TYPE_STRING, $nullable),
];
}
// continue how it used to be
if (!$builtinType = $this->getPhpType($typeOfField)) {
return null;
}
I went for option D because I really want to have bigint
typed to int
and not to string
as I have a project that
only delivers an API with API Platform. So keeping my JSON Schema consistent is top priority.
It works, but I am not sure if it is a) a solution and b) a preferred solution.
How to reproduce
I can create a simple project if the description above is not enough. The easiest way would be to:
- use API Platform with one resource and pgsql for storage.
- use orm 3 and dbal 4
- add 1 property with bigint
- send a JSON body with int values in your property
- The type of the "bigint" attribute for class "IHaveNumbers" must be one of "string" ("int" given).
Possible Solution
In case DoctrineExtractor
cannot be updated one can also play around with attributes. Below I have my example in my project:
#[ORM\Column(type: Types::BIGINT)]
#[ApiProperty(
jsonSchemaContext: ['type' => 'integer'],
extraProperties: [SchemaPropertyMetadataFactory::JSON_SCHEMA_USER_DEFINED => true]
)]
#[Context(denormalizationContext: [AbstractObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true])]
#[Assert\Type(type: 'int')]
#[Assert\Positive]
#[Assert\Range(max: 12_345_678_901)]
private $bigInt;
Additional Context
No response