Convert single property for response with the FOSRestBundle in Symfony 5 - php

I'm working with numbers with a lot of decimal places in my symfony application. In my doctrine entity I have for example this property:
/**
* #ORM\Column(type="float")
*/
private float $value;
In my mysql database I have this value for example: 0.00000000020828579949508
When I dump that in PHP I'm getting this: float(9.3722658865184E-7). I also made an API with the FOSRestBundle. In that API I want to return the value not in exponential form with at least 12 of it's decimal places. I think in that case I have to provide the value as string, correct? I figured out that I can convert it to string with something like this: sprintf("%.12f", $myEntity->getValue()). But I have two questions now:
How can I convert a single property for response with the FOSRestBundle? So that I return the "value" property as string, even if it is a float normally.
Is there a general best practice or any tips to work with such numbers in symfony, doctrine and the FOSRestBundle?
Right now this is my controller action:
public function getData(): Response
{
$repository = $this->getDoctrine()->getRepository(MyEntity::class);
$data = $repository->findAll();
return $this->handleView($this->view($data));
}

Assuming you are using Symfony version 3.x, you can use the JMS #Expose and #Accessor annotations to control how your entity property is serialized into response data. More info here. However, consider that JMS is bypassed if all you want is to pass your entity to the Twig template rendering engine.
The Doctrine float column type is the correct type to use when handling larger sized floating point numbers such as yours. Your issue is more related to the scientific notation that PHP is designed to use in order to represent these large floats. Allowing arithmetic such as 2.1E-10 + 2.2E-9, but also requiring extra steps to convert their notation to a perhaps more human friendly readable form by using functions such as sprintf and number_format.

Related

Laravel service and UUID location

I have a method in the controller that calls a service, specifically this method that returns quotes for activities. I'm unsure about the correctness of the logic. Is it correct to pass the parameters as UUIDs to the service and then convert them internally to obtain the internal ID? Internally, I work with IDs, while publicly, I expose UUIDs.
Thank you!
public function getQuotes(string $businessUUID, string $businessTypeUUID, array $filters): Collection
{
// Get the internal id from the uuid
$filters['business_id'] = $this->businessService->getBusinessByUUID($businessUUID)->id;
$filters['business_type_id'] = $this->businessTypeService->getBusinessTypeByUUID($businessTypeUUID)->id;
// Retrieve the quotes that match the given filters
$quotes = BusinessQuote::query()->withFilters($filters)->get();
// If no quotes match the given filters
if ($quotes->isEmpty()) {
throw new ModelNotFoundException(__('Quotes not found'));
}
// Return the quotes
return $quotes;
}
Yes, this approach is correct and safe because a chance to guess UUID is currently impossible. Using integer IDs you get better performance than working with UUID internally.
Keep in mind, is recommended to use UUID v4 for maximum security.

Error with Symfony Serializer can't normalize int to float

I get an error when I try to denormalize an object with a float attribute but an int value.
Symfony\Component\Serializer\Exception\NotNormalizableValueException:
The type of the "solde" attribute for class
"Wallet" must be one of "float" ("int"
given).
My Serializer configuration:
class SerializerService implements SerializerInterface, NormalizerInterface, DenormalizerInterface
{
public const JSON_FORMAT = 'json';
/**
* #var Serializer
*/
private $serializer;
/**
* #var LoggerInterface
*/
private $logger;
/**
* #param LoggerInterface $logger
*/
public function __construct(
LoggerInterface $logger
) {
$this->logger = $logger;
$this->serializer = new Serializer([
new DateTimeNormalizer(),
new ObjectNormalizer(null, null, null, new ReflectionExtractor()),
new ArrayDenormalizer()
], [
new JsonEncoder(
new JsonEncode(),
new JsonDecode(),
)
]);
}
...
...
I didn't find anything relevant in the Symfony Serializer documentation.
Can you help me please?
The problem is not so much with the serializer, but your data. You probably have something like:
{
“solde”: 1.0
}
but it should be: {“solde”: 1}, i.e. your data should be an int and not a float.
If you make $solde in your Wallet-object a float, instead of an int you won’t have this problem because converting from int to float is usually lossless, i.e. 1 to 1.0. Also, you should not accept floats for this kind of data in the first place. Assuming “1.0” stands for something like 1$ or 1 EUR, then it is customary to instead save it as a string or int as cent value, i.e. 100 instead and I would recommend also using this format for your JSON. That way, you don’t need floats and you won’t run into weird floating point issues. I highly recommend going that route. Since you are doing the conversion the other way (from float to int) you will lose precision, i.e. 1.5 will either become 1 or 2, which obviously is bad because your users might not know which of the 2 possible outcomes it will be.
Coming back to your error. In your object you can resolve this type issue either when you receive the data, i.e. set the field from JSON or whenever you read the data. For the former do the lossy conversion (casting to int, rounding, …) inside the object’s setter for this field. You might need to adjust your ObjectNormalizer to use the getters and setters for this, but it should use the PropertyAccess-component by default which should use the setter before it looks for the property, if I am not mistaken. It would roughly look like this:
class Wallet
{
private int $solde;
// …
public function setSolde(float|int $solde): void
{
$this->solde = (int) $solde; // casting to int will just cut off anything after the “.” in floats
}
}
You could also have your field be either float or int and then do the conversion on the getter, i.e. when you use the field but then you might get issues when you want to persist the data in the database.
class Wallet
{
private int|float $solde;
// …
public function getSolde(): int
{
return (int) $this->solde; // casting to int will just cut off anything after the “.” in floats
}
}
If you don’t want to adjust your object for this, then you can teach the Serializer to do the lossy conversion by writing a custom denormalizer (Symfony combines denormalizers and normalizers in their normalizers, i.e. ObjectNormalizer, if you are looking for a reference how to write them). This denormalizer will then handle your object, i.e. Wallet, and manually map each field from your JSON to the object’s field, including “solde”, where you have the type error. You can then resolve the type error in the Denormalizer by doing the lossy conversion (casting to int, rounding, …). You have to be careful to not make the Denormalizer too generic as you might end up doing this even in places where you don’t want this. Symfony has a part in the docs for custom normalizers/denormalizers, so it should be fairly straightforward. If you really need this, I would recommend going this route instead, assuming you only need to worry about this problem when reading the data from JSON.

How to force int to string in Laravel JSON?

I've recently updated my server to a newer version of MySQL and PHP 7 for various reasons. On my previous instance, running PHP 5.5, Laravel's response()->json() always converted tinyint's into a string. Now running newer server software, it's returning me int's -as it should...
I'd have to change a lots of my codebase to either cast types / convert them into a string manually, whic I'm trying to avoid at the moment.
Is there a way to somehow force response()->json() to return int's as string's?
Is there a way to somehow force response()->json() to return int's as string's
I don't want to change the code base - do not want to cast types, convert it,
No. There's no option for that. You need to do that yourself if needed.
There is a way to cast integer into string in laravel
in your model you can cast id to string. Its as follows
protected $casts = [ 'id' => 'string' ];
But the downside is that you would have to do that for all Models.
If you don't want to modify a lot of code you could run response data through a quick and dirty function. Instead of going directory to JSON you should instead grab the data as a nested array. Then put it through a function like this:
function convertIntToString ($myArray) {
foreach ($myArray as $thisKey => $thisValue) {
if (is_array($thisValue)) {
// recurse to handle a nested array
$myArray[$thisKey] = convertIntToString($thisValue);
} elseif (is_integer($thisValue)) {
// convert any integers to a string
$myArray[$thisKey] = (string) $thisValue;
}
}
return $myArray;
}
The function will convert integers to strings and use recursion to handle nested arrays. Take the output from that and then convert it to JSON.
The best solution for me is to to use attribute casting and
Fractal transformers
Fractal transformers are extremely useful when you have complex responses with multiple relations included.
You can typecast it to string:
return response->json(["data" => (string)1]);

Malformed field names in Symfony JsonResponse

I have a strange problem with Symfony's JsonResponse that I cannot seem to figure out. I have the following action in my controller:
public function loadTemplateAction($id)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Host');
$template = $repository->find($id);
return new JsonResponse((array)$template);
}
It's supposed to find the given template in my repository by the passed id. I want to use the returned data in an ajax call. It does what I want, but it seems to "prefix" all the field names with an asterisk. So it returns a response like this:
I can't figure out why it's putting those asterisks in front of the field names (they are obviously not named that way in my datasource). Does anybody have a clue what could be causing this type of behaviour?
First of all, see http://php.net/manual/en/language.types.array.php:
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible; private variables have the class name prepended to the
variable name; protected variables have a '*' prepended to the
variable name. These prepended values have null bytes on either side.
This can result in some unexpected behaviour:
You probably should not just typecast your object to arrays and JSON encode them. Have a look at some of the serialization solutions that exist:
http://symfony.com/doc/current/cookbook/serializer.html
http://jmsyst.com/libs/serializer
These libraries offer great control on how to serialize your objects to different formats, including JSON.
If you need less control on how your objects are serialized to JSON, you could just implement the JsonSerializable interface.

JMSSerializerBundle transformation on specific type

I'm using the JMSSerializerBundle in a Symfony2 project, combined with Doctrine2.
I've defined a class property as double value, as it represents a product price.
/**
* Price of product
*
* #Type("double")
* #MongoDb\Float */
private $price;
It's a requirement to display values with trailing zeros (100.00 instead of 100). I'd like to hook this transformation into the deserialization, as the serializer actually knows he's working with a double value.
Casting all numeric values afterwars won't work, as there's other propertys which must not be affected (like quantity, those must stay integer).
Any ideas/hints on this issue?
JMSSerializer bundle allows you to define custom serializer/deserializer handler: http://jmsyst.com/libs/serializer/master/handlers
This allows you to change the way of how a specifc type is being serialized or deserializer.

Categories