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.
Related
According to Doctrine documentation, one can order a field in an entity with #OrderBy({"name" = "ASC"}). With PHP 8 attributes, a field definition may look like this:
#[ORM\Column(length: 25)]
private ?string $food_name;
However, adding a sort like this: #[ORM\OrderBy({"food_name" = "ASC"})] is not acceptable syntax. Nor is #[ORM\OrderBy("food_name" = "ASC")].
Acceptable syntax such as #[ORM\OrderBy('food_name = ASC')] results in
Doctrine\ORM\Mapping\OrderBy::__construct(): Argument #1 ($value) must be of type array, string given
For this use case, Food is a collection field in a ManyToMany relationship with Meal. I'm trying to accomplish a sorted field so that a template that uses {% for food in meal.foods %} will render a pre-sorted food name list.
A) Can this be accomplished in the Food entity? or
B) Should this be done in the Meal entity? or
C) Is a presorted field not possible?
If presorted is possible, what is the correct syntax?
Add the attribute on Meal.foods:
#[ORM\OrderBy(["food_name" => "ASC"])]
private $foods;
See also the documentation here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.13/reference/attributes-reference.html#attrref_orderby
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.
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.
I have an Entity with an attribute defined as following:
/**
* #var integer
*
* #ORM\Column(name="weight", type="integer")
*/
private $weight;
I was trying to solve a bug and used var_dump() to have some clue of what was happening...
The response was:
string '20' (length=2)
I don't understand why $weight is returning as string... shouldn't it be an integer?
int 20
Or should I handle it in my business logic?
Edit (how I called var_dump()):
I have a class named "Calculator" that iterates $items and use the $weight attribute. Is something like this:
Controller:
$calculator->calculate($category->getItems());
Calculator:
foreach($items as $item) {
//logic...
var_dump($item->getWeight());
}
Edit (database column schema):
Here is the field in database:
weight int(11)
What version of PHP and MySQL driver are you using ?
I don't know exactly how Symfony casts (or not) the database results, but if you're using PHP 5.2, it can be quite normal : any variable you get from MySQL in PHP is always a string, even if it's an integer in the database.
In PHP 5.3, it's different, if you use the correct driver (mysqlnd — See http://blog.ulf-wendel.de/2008/pdo_mysqlnd-the-new-features-of-pdo_mysql/)
See this answer for more details :
https://stackoverflow.com/a/2430657/1741150
Hope that helps !
This question already has answers here:
PHPDoc for variable-length arrays of arguments
(7 answers)
Closed 5 years ago.
What's the best way to document the elements of an array when it is a parameter to a method? For example, using the PHPDoc headers, I might have something like:
#param array $data
What this doesn't tell me is what elements are mandatory in the array, and what are optional elements. I imagine this should go in the explanation of the method. Something like:
array: $data
============
int $id Required
name $string Required
town $string Optional
/**
* #param array $data
*
* #var $data[id] int, required
* #var $data[name] string, required
* #var $data[town] string, required
*/
This example of using doctrine and zf2 example:
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
* #Form\Attributes({"type":"hidden"})
*/
protected $id;
/**
* #ORM\Column(type="string")
* #Form\Type("Zend\Form\Element\Text")
* #Form\Required({"required":"true"})
* #Form\Filter({"name":"StripTags"})
* #Form\Filter({"name":"StringTrim"})
* #Form\Validator({"name":"StringLength", "options":{"min":"5"}})
* #Form\Options({"label":"First name"})
*/
protected $firstName;
If you have such a complex array with constraints for every single member, I wouldn't use an anonymous array but rather a well defined object. With an array, you can never be sure what it holds, that's somewhat like passing "Object" in e.g. Java, which you rarely would consider a good choice.
However, there is the possibility of a little hinting, when you array contains objects of a certain type as explained here, but that's not a really good answer to your question.
If you really need the parameter as an array, you might document it the way you proposed in the method's description; however, if you use an object as parameter, you'd have additional support in modern IDEs (IntelliSense and so on).
EDIT: I mean, for me the question would be "why would I want to use an anonymous array instead of a self defined type" - and besides simplicity (which will backfire as technical debt later if you maintain and extend your code), I cannot think of a reason, especially compared to what you gain in using a user defined type (self documented code, constraints visible and made explicit by standard methods and so on).
If you just need a dump of data, you might want to go with a simple array, but since you're already thinking about optional and required keys, that screams for a user defined type.
EDIT2: Regarding your comment about if your already have an array as source: I'm not sure whether you need to pass it on as an array or do "mapping" operations as soon as you receive the array (e.g. as $_POST or as return value from some third party library or PHP internal functions or such).
I suppose one could argue that it's not the model's business to interpret data generated by views (e.g. HTML forms which POST data), but rather the controller's resonsibility to react accordingly to the input and transfer the model in the appropriate state. What I mean by this is that you could do something like this if you receive e.g. an array as $_POST:
$customer = new Customer();
$customer->setId($_POST['id']);
$customer->setName($_POST['name']);
$customer->setTown($_POST['town']);
And handle errors as soon as you access the $customer, e.g. throwing exceptions if the name is not set (i.e. $_POST['name'] was empty or such). This way, you use the source array to call setters on the object instead of e.g. passing the array to a factory like Customer::buildByHttpPostData(array $data) and thereby delegating the knowledge of the view's details (names of HTML input tags and such).
Bottom line is, there's no "standard" way to declare required or optional array keys, and of course you can describe those constraints in the method description, but perhaps you can circumvent this by keeping with supported ways like PHPDoc comments on setters or getters.
Of course, there may be better ways to approach the problem, and perhaps somebody comes up with a better answer how to handle it.
To answer the question there is no formal way, try to use a way that you think is most intuitive. I do something similar:
/**
* #param array $data [ int $id, string $name, string $town ]
*/
However I wouldn't use this notation for parameters but rather as return values. In your case I would extract the method arguments to an object and pass that into the method instead:
/**
* #param User $user
*/
public function myMethod( User $user )
{
//...
}
The reason for this is the User object exposes it's properties as an API to other developers, self documenting code!
The other method would be to separate out the array elements into arguments like so:
/**
* #param int $id
* #param string $name
* #param string $town
*/
public function myMethod( $id, $name, $town )
{
//...
}
3 arguments is just about passable but you should start looking for a way to refactor it, like my first suggestion. 4 arguments is generally agreed to be messy and you refactor it.