How to disable automatic casting of object properties? - symfony

I am using JMS serializer bundle to serialize and deserialize data within restful api. I have the following scenario:
Entity:
/**
* Settings
*
* #ORM\Table(name="user_settings")
* #ORM\Entity
*
* #JMS\ExclusionPolicy("none")
* #JMS\AccessType("public_method")
*/
class Settings
{
/**
* #var boolean
*
* #ORM\Column(name="search", type="boolean")
*
* #JMS\Groups({"get", "update"})
*/
private $search;
}
I have configured JMS to use Doctrine object constructor.
When I make a POST and deserialise the data into the Settings object, it works, but in a scenario like this:
{"id":5, "search":"string"}
It converts the string "string" into a boolean automatically. I believe it has something to do with doctrine. If I modify the ORM column annotation to type="string", the casting doesn't happen, which is what I want, but I want to keep type="boolean".
With the current situation, I cannot validate the object and say that "string" is not a valid value, because it is casted to boolean before I can do any validation on the object.
If you need further explanation, please let me know, and I would really appreciate your help.

I have found a solution for this.
However, I didn't find any configuration possibilities for the bundle itself, but it was possible to override the following parameter:
<parameter key="jms_serializer.json_deserialization_visitor.class">MyBundle\CoreBundle\Serializer\JsonDeserializationVisitor</parameter>
The default GenericDeserializationVisitor was using the methods like visitBoolean(), and casting boolean types to boolean, which was my case.

Related

Deserialize and persist relationships with JMS Serializer

I'm trying to get the following working:
I've got an entity like:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Contact
*
* #ORM\Table()
* #ORM\Entity()
*/
class Contact
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts")
* #ORM\JoinColumn(name="service_client", referencedColumnName="service_client")
*
* #JMS\Type("AppBundle\Entity\ServiceClient")
* #JMS\SerializedName("serviceClient")
*/
private $serviceClient;
}
I'm sending the following JSON over an HTTP request (Post, it's a new Contact, no ID):
{
"name": "Lorem Ipsum",
"serviceClient": {"service_client": "ipsum"}
}
What I expect is for the JMS Serializer to parse that relationship, and leting me persist the Contact object like this:
<?php
$contact = $this->get('serializer')->deserialize(
$request->getContent(),
Contact::class, 'json'
);
$this->em->persist($contact);
$this->em->flush();
In fact I got that working (I swear it was working) but now it's giving me the follwing error:
A new entity was found through the relationship
'AppBundle\Entity\Contact#serviceClient' that was not configured to
cascade persist operations for entity:
AppBundle\Entity\ServiceClient#000000006fafb93e00007f122bd10320. To
solve this issue: Either explicitly call EntityManager#persist() on
this unknown entity or configure cascade persist this association in
the mapping for example #ManyToOne(..,cascade={\"persist\"}). If you
cannot find out which entity causes the problem implement
'AppBundle\Entity\ServiceClient#__toString()' to get a clue."
So it's tryign to persist the entity... a thing I do not want since the entity already exists. I just want Doctrine to put the reference, the foreign key.
Edit: It seems it's the constructor, if I set it to the doctrine_object_constructor it works like magic, the thing I do not understand is why it stop working in the first place.
Can anyone share any ideas or a cleaner way to do what I did?
jms_serializer.object_constructor:
alias: jms_serializer.doctrine_object_constructor
public: false
This problem happens when Doctrine cannot map your relationship to an existing record in the database, so it will try to create a new one with the data from the JSON object.
In your case, the JSON object: {"service_client": "ipsum"} cannot be mapped to an existing ServiceClient instance.
It's because the default JMS object constructor call the unserialize function (will be the one from your Entity if you defined this method) to construct the object, which mean this object will always be treated by Doctrine as new (has never been persisted).
By using doctrine_object_constructor, JMS will get the object from Doctrine. The object came from Doctrine not only have the attributes and methods you define in your entity, but also meta-data about whether it's an existing one, it's corresponding row from the database ( so Doctrine can detect update made on the record later and handle it), therefore Doctrine are able to avoid incorrect persisting.
Doctrine will try to persist the Contact with a reference of a ServiceClient entity given in the deserialization. In the entity definition at the level of the manyToOne definition you need to add :
#ORM\ManyToOne(targetEntity="AppBundle\Entity\ServiceClient", inversedBy="contacts", cascade={"persist"})

Symfony2 & FOSRestBundle: Getting UUID packed in a BINARY(16) field from MySQL

I am facing a weird problem relating to UUIDs.
I have developed a REST API using Symfony2+FOSRestBundle+JMSSerializer. As I need to update some tables from two sources I thought of using UUID as primary key for one entity.
I did a doctrine:mapping:import to generate entities in my Symfony project. Everything correct. I ended up with the following entity (only exposing the key field and generated getter for simplicity):
<?php
namespace Stardigita\TgaAPIBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* TgaBookings
*
* #ORM\Table(name="tga_bookings", indexes={[...]})
* #ORM\Entity
*/
class TgaBookings
{
/**
* #var string
*
* #ORM\Column(name="book_cd_booking_UUID", type="blob", length=16, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $bookCdBookingUuid;
/**
* Get bookCdBookingUuid
*
* #return string
*/
public function getBookCdBookingUuid()
{
return $this->bookCdBookingUuid;
}
...
No setter was generated. I can still do it myself and I will, as I will need to know the key beforehand.
The data for this field is correctly stored in the table as a BINARY(16). When I recover the data calling the REST GET method, I get this:
[
{
"book_cd_booking_uuid": "Resource id #1244",
"book_cd_booking": 8,
....
My question is: how can I get the actual data from the field?
I suppose something has to be done in the field getter, but I tried some solutions without any success.
Thanks.
UPDATE:
I've managed to get the actual data logged, modifying the getBookCdBookingUuid method this way:
/**
* Get bookCdBookingUuid
*
* #return string
*/
public function getBookCdBookingUuid()
{
return bin2hex($this->bookCdBookingUuid);
}
and changed the type to "guid" in the property annotation:
/**
* #var string
*
* #ORM\Column(name="book_cd_booking_UUID", type="guid", length=16, nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $bookCdBookingUuid;
I have represented the hex UUID correctly in the log before returning the results in the controller:
[2014-11-03 19:52:07] app.ERROR: 1046684e5f6711e4a09f00089bce936a [] []
But still getting an exception relating UTF invalid characters:
request.CRITICAL: Uncaught PHP Exception RuntimeException: "Your data could not be encoded because it contains invalid UTF8 characters." at /var/www/tga_api/vendor/jms/serializer/src/JMS/Serializer/JsonSerializationVisitor.php line 36 {"exception":"[object] (RuntimeException: Your data could not be encoded because it contains invalid UTF8 characters. at /var/www/tga_api/vendor/jms/serializer/src/JMS/Serializer/JsonSerializationVisitor.php:36)"} []
Also I got no response from the service. A 500 error is returned.
Please, I need to solve this issue. Any ideas are welcome.
Thanks.
GeneratedValue
I notice you're using the annotation #ORM\GeneratedValue(strategy="IDENTITY") for the UUID property. IDENTITY means the database should/will use auto-increments, which shouldn't be done when using UUIDs. Please change it to #ORM\GeneratedValue(strategy="NONE") or remove it completely.
Conversion
The string form of a UUID (like 01234567-89ab-cdef-0123-456789abcdef) should be converted to binary when it's persisted in the database, and converted back when fetched from the database.
The easiest way to do this is to introduce a custom type. See here for an example.
Bugs
Doctrine (even master/2.5) has some issues with using UUIDs in associations. I'm attempting to fix these issues in PR #1178.
If you need UUIDs in associations and can't wait till it's fixed, then use regular integer ids and have the UUID is a separate column.

Custom metadata information in Symfony/Doctrine

Is it possible to create custom metadata information in the entities? Something that would use the already present functionality of using either Annotation, yml or xml to store metadata about the entity.
Example:
/**
* #var string
*
* #ORM\Column(name="text", type="text")
* #CUSTOM\Meta(key="value") // <-- Extra information
*/
protected $text;
For what I've been researching, it seems that I should use the functionality of ClassMetadataFactory. Is it possible, or would I have to make it from scratch?

Symfony2 - One ManyToOne relation on one field referencing two entities

I have an entity which stores "removal requests" to either studios or models. An object (Studio or model can have many requests).
Entity RemovalRequest has a field named : object.
I would like to know if it's possible to do something like this in RemovalRequest entity:
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Studio", inversedBy="requests")
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Model", inversedBy="requests")
*/
private $object;
I can't find anything about this special case over Internet..
If it's not possible, I'm open to any suggestions you might have !
Do you realy need a new entity to store information about removal? Maybe just add a flag to Studio and Model:
/**
* #ORM\Column(name="is_to_remove", type="boolean")
*/
$isToRemove = false;
If you need RemovalRequest entity you should add two properties fore each type like this:
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Model", inversedBy="requests")
*/
$model;
/**
* #ORM\ManyToOne(targetEntity="Project\GestionBundle\Entity\Studio", inversedBy="requests")
*/
$studio;
It is bed idea to store two different classes in one property

copy object Inheritance Mapping doctrine2

i have the following entity:
/**
* #ORM\Table(name="event")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="eventtype", type="integer")
* #ORM\DiscriminatorMap({1 = "eventClub", 2 = "eventLive", 3 = "eventBar", 4 = "eventGeneric" })
*/
class P1event extends AbstractEntity {
/**
*
* #var List[] $lists
*
* #ORM\OneToMany(targetEntity="List", mappedBy="fkevent", cascade={"persist", "merge"})"
*/
private $lists;
A user should have the possibility to change the eventtype via a form. By changing the evetntype, i must create a new Object becaus of my table inheritance (doctrine doc).
I have no idea how i can change the lists of the copied event to the new event inside one transaction. Has anybody an idea how to handle it correctly? Thank you very much.
In a similar situation, I just passed GET parameter type, then in the controller created an object of the desired type and transferred it to the form. If I understand your question correctly.
After created a new event object, call
$eventY->setLists($eventX->getLists());
doen't work ?

Resources