How to update a Doctrine Entity from a serialized JSON? - symfony

We are using Symfony2 to create an API. When updating a record, we expect the JSON input to represent a serialized updated entity. The JSON data will not contain some fields (for instance, CreatedAt should be set only once when the entity is created - and never updated). For instance, here is an example JSON PUT request:
{"id":"1","name":"anyname","description":"anydescription"}
Here is the PHP code on the Controller that should update the entity according to the JSON above (we are using JMS serializer Bundle):
$supplier = $serializer->deserialize(
$this->get('request')->getContent(),
'WhateverEntity',
'json'
);
The EntityManger understands (correctly) that this is an update request (in fact, a SELECT query is implicitly triggered). The EntityManager also guess (not correctly) that CreatedAt property should be NULLified - it should instead keep the previous one.
How to fix this issue?

It's possible as well to do it with Symfony Serializer using object_to_populate option.
Example: I receive JSON request. If record exists in database I want to update fields received in body, if it does not exist I want to create new one.
/**
* #Route("/{id}", methods={"PUT"})
*/
public function upsert(string $id, Request $request, SerializerInterface $serializer)
{
$content = $request->getContent(); // Get json from request
$product = $this->getDoctrine()->getRepository(Product::class)->findOne($id); // Try to find product in database with provided id
if (!$product) { // If product does not exist, create fresh entity
$product = new Product();
}
$product = $serializer->deserialize(
$content,
Product::class,
'json',
['object_to_populate' => $product] // Populate deserialized JSON content into existing/new entity
);
// validation, etc...
$this->getDoctrine()->getManager()->persist($product); // Will produce update/instert statement
$this->getDoctrine()->getManager()->flush($product);
// (...)

using the JMSSerializerBundle follow the install instructions at
http://jmsyst.com/bundles/JMSSerializerBundle
either create your own serializer service or alter the JMSSerializerBundle to use the doctrine object constructor instead of the simple object constructor.
<service id="jms_serializer.object_constructor" alias="jms_serializer.doctrine_object_constructor" public="false"/>
This basically handles exactly what Ocramius solution does but using the JMSSerializerBundles deserialize.

I would use the Doctrine\ORM\Mapping\ClassMetadata API to discover existing fields in your entity.
You can do following (I don't know how JMSSerializerBundle works):
//Unserialize data into $data
$metadata = $em->getMetadataFactory()->getMetadataFor($FQCN);
$id = array();
foreach ($metadata->getIdentifierFieldNames() as $identifier) {
if (!isset($data[$identifier])) {
throw new InvalidArgumentException('Missing identifier');
}
$id[$identifier] = $data[$identifier];
unset($data[$identifier]);
}
$entity = $em->find($metadata->getName(), $id);
foreach ($metadata->getFieldNames() as $field) {
//add necessary checks about field read/write operation feasibility here
if (isset($data[$field])) {
//careful! setters are not being called! Inflection is up to you if you need it!
$metadata->setFieldValue($entity, $field, $data[$field]);
}
}
$em->flush();

Related

Receive all kind of Entity as an Argument in Symfony

I have a method to get a change set of Entity from the unit of work (entity manager) in Symfony and I would like it to receive all Entities (Post, User...) instead of specific Entity.
public function getChanges(Post $event): array
{
$uow = $this->entityManager->getUnitOfWork();
$uow->computeChangeSets();
return $uow->getEntityChangeSet($event);
}
Do you have any idea to do it?
One solution is getting the object as the argument but I prefer to get only the Symfony Entity objects in the function.
Look for doctrine entity listener.
https://symfony.com/doc/current/doctrine/events.html#doctrine-entity-listeners
And do not filter entity inside it, remove this part from the example:
// if this listener only applies to certain entity types,
// add some code to check the entity type as early as possible
if (!$entity instanceof Product) {
return;
}
if (!$this->entityManager->contains($entity)) {
throw new Exception('The given object must be doctrine entity');
}

What is the best way to create a singleton entity in Symfony 4?

I want to create a settings page, which only has a form in it. If the form is submitted it only updates settings entity but never creates another one. Currently, I achieved this like:
/**
* #param SettingsRepository $settingsRepository
* #return Settings
*/
public function getEntity(SettingsRepository $settingsRepository): Settings
{
$settings = $settingsRepository->find(1);
if($settings == null)
{
$settings = new Settings();
}
return $settings;
}
In SettingsController I call getEntity() method which returns new Settings entity (if the setting were not set yet) or already existing Settings entity (if setting were set at least once).
However my solution is quite ugly and it has hardcoded entity id "1", so I'm looking for a better solution.
Settings controller:
public function index(
Request $request,
SettingsRepository $settingsRepository,
FlashBagInterface $flashBag,
TranslatorInterface $translator,
SettingsService $settingsService
): Response
{
// getEntity() method above
$settings = $settingsService->getEntity($settingsRepository);
$settingsForm = $this->createForm(SettingsType::class, $settings);
$settingsForm->handleRequest($request);
if ($settingsForm->isSubmitted() && $settingsForm->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($settings);
$em->flush();
return $this->redirectToRoute('app_admin_settings_index');
}
return $this->render(
'admin/settings/index.html.twig',
[
'settings_form' => $settingsForm->createView(),
]
);
}
You could use Doctrine Embeddables here.
Settings, strictly speaking, should not be mapped to entities, since they are not identifiable, nor meant to be. That is, of course, a matter of debate. Really, a Settings object is more of a value object than an entity. Read here for more info.
So, in cases like these better than having a one to one relationship and all that fuzz, you probably will be fine with a simple Value Object called settings, that will be mapped to the database as a Doctrine Embeddable.
You can make this object a singleton by creating instances of it only in factory methods, making the constructor private, preventing cloning and all that. Usually, it is enough only making it immutable, meaning, no behavior can alter it's state. If you need to mutate it, then the method responsible for that should create a new instance of it.
You can have a a method like this Settings::createFromArray() and antoher called Settings::createDefaults() that you will use when you new up an entity: always default config.
Then, the setSettings method on your entity receieves only a settings object as an argument.
If you don't like inmutablity, you can also make setter methods for the Settings object.

How to serialize a custom findBy() method?

When I use findBy(), my data is automatically serialized.
$users = $this->getDoctrine()->getRepository('AppBundle:User')->findBy($params, $orderBy, $limit, $offset);
/**
* #VirtualProperty
* #SerializedName("catagory")
* #Groups({"user"})
*/
public function getCategoryId()
{
return $this->category->getId();
}
But when I use a custom MyfindBy() from my repository, the data is not automatically serialized. How can I automatically serialize the data?
SOLUTION :
Don't use select at the querybuilder.
Please follow the documentation of symfony serializer from visiting this https://symfony.com/doc/current/components/serializer.html
(follow your desired verson documentation from there)
After getting your objecs by custom query u can pass the whole object array named $persons to the $jsonContent = $serializer->serialize($persons, 'json'); and send it to your response . after installing serializer components to your project .
please inform me if it not works for you.Thank you.

How to validate entity properties

I'm writing an import script for our database (which is running on mySql) from CSV files. Since importing with doctrine entities is so slow and memory intensive, I'm opting for the option to write native queries to do the import task.
However, before the actual import, I need to validate the values in the csv file, and I wonder if there is any way to make use of the entity properties definition (already defined in the orm xml files) to do the validation. For example, if that field is already defined as a string with max 255 char in length then I can some how grab that definition and do the validation of the value in the csv file.
I hope it makes sense, please let me know if my question is not clear at any part.
You could use the Symfony2 validator service to check data before importing it. However you would have to add the max length constraint as an assertion.
Example entity:
<?php
// src/Acme/YourBundle/Entity/Author.php
// ...
use Symfony\Component\Validator\Constraints as Assert;
class YourEntity
{
/**
* #Assert\Length(max=255)
*/
public $someString;
}
Your controller which handles the import:
<?php
// ...
use Acme\YourBundle\Entity\YourEntity;
public function indexAction()
{
//omitted: get your csv data first
// create a new instance of your entity
$entity = new YourEntity();
// populate your entity with data from your csv file
$entity->setSomeString($stringFromCsvFile);
// get the validator and validate your entity
$validator = $this->get('validator');
$errors = $validator->validate($entity);
if (count($errors) > 0) {
// there are errors! do something with them
} else {
// there are no errors, persist the entity
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
}
}
See http://symfony.com/doc/current/book/validation.html for more information.

Symfony2 get validation constraints on an entity

Im working on a method to get all validation constraints of an entity (what i am trying to achieve is to return this data in JSON and apply the same constraints on client side using JQuery Validation Plugin), however im having some trouble getting the constraints,
Here is my current code:
$metadata = new \Symfony\Component\Validator\Mapping\ClassMetadata("Namespace\JobBundle\Entity\Job");
$annotationloader = new AnnotationLoader(new AnnotationReader());
$annotationloader->loadClassMetadata($metadata);
what i get in $metadata is an empty array for the constraints attribute, the rest ($properties and $members have only the error messages... but not the actual constraints (eg : required, integer...)).
What im a doing wrong?
I would probably use the validator service instead of instantiating a new class metadata. You never know if some classes are initialized through the service.
$metadata = $this->container
->get('validator')
->getMetadataFactory()
->getClassMetadata("Name‌​space\JobBundle\Entity\Job");
and $metadata should have the data you are looking for
Symfony 2.3 and above
$metadata = $this->container
->get('validator')
->getMetadataFor("Name‌​space\JobBundle\Entity\Job");
private function getValidations()
{
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new yourentity());
$constrainedProperties=$metadata->getConstrainedProperties();
foreach($constrainedProperties as $constrainedProperty)
{
$propertyMetadata=$metadata->getPropertyMetadata($constrainedProperty);
$constraints=$propertyMetadata[0]->constraints;
foreach($constraints as $constraint)
{
//here you can use $constraint to get the constraint, messages etc that apply to a particular property of your entity
}
}
}
$validator=$this->get("validator");
$metadata=$validator->getMetadataFor(new yourentity());
The object $metadata now contains all the metadata about validations that concerns your specific entity.

Resources