Convert json encoded String to json object in FosRestBundle - symfony

I have a json encoded string in one of my DB fields, e.g.
[{"name":"car","price":"10"}]
I'm using the FosRestBundle to return the DB-Values in json format and the String above is returned - as String - nothing special until here;)
How can this String be converted, so that a Json Object is returned instead?

Finally I found the solution.
My Entity contained this:
/**
* #var string
*
* #ORM\Column(name="options", type="string", nullable=true)
*/
private $options;
"options" contained the json encoded string. So I tried it with the JMS Serilizer Annotation #Accessor and wrote this specific getter:
/**
* Get optionsAsArray
*
* #return array
*/
public function getOptionsAsArray()
{
return (array)json_decode($this->options, true);
}
Still got an error "Array to string conversion". So the Solution was, to add another Annotation #type and the JMSSerializer returned nicely formatted JSON.
This is what the Entity has to look like:
use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\Type;
/* ... */
/**
* #var string
*
* #ORM\Column(name="options", type="string", nullable=true)
* #Accessor(getter="getOptionsAsArray")
* #Type("array")
*/
private $options;

You can decode this string to an stdClass or an associative array with json_decode. Is this what you are looking for?
EDIT : This should work
public function myAction()
{
// do stuff
$string = '[{"name":"car","price":"10"}]';
$array = json_decode($string, true);
/* array is like
[
0 => [
'name' => string(3) "car"
'price' => string(2) "10"
]
]
*/
return $array;
}

Related

Handling imporper data during deserialization when using Symfony Serializer Component

I am new to the Symfony serializer component. I am trying to properly deserialize a JSON body to the following DTO:
class PostDTO
{
/** #var string */
private $name;
/**
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #param string $name
*/
public function setName(string $name): void
{
$this->name = $name;
}
}
The controller method as follows:
/**
* #Route (path="", methods={"POST"}, name="new_post")
* #param Request $request
* #return Response
*/
public function create(Request $request): Response
{
$model = $this->serializer->deserialize($request->getContent(), PostDTO::class, 'json');
// call the service with the model
return new JsonResponse();
}
My problem is that I wanted to handle business-validation after the body was deserialized. However, if i specify an invalid value for the name, such as false or [], the deserialization will fail with an exception: Symfony\Component\Serializer\Exception\NotNormalizableValueException: "The type of the "name" attribute for class "App\Service\PostDTO" must be one of "string" ("array" given)..
I do understand that it is because I intentionally set "name": []. However, I was looking for a way to set the fields to a default value or even perform some validation pre-deserialization.
I have found the proper way to handle this. That exception was thrown because the serializer was not able to create the PostDTO class using the invalid payload I have provided.
To handle this, I have created my custom denormalizer which kicks in only for this particular class. To do this, I have implemented the DenormalizerInterface like so:
use App\Service\PostDTO;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
class PostDTODeserializer implements DenormalizerInterface
{
/** #var ObjectNormalizer */
private $normalizer;
/**
* PostDTODeserializer constructor.
* #param ObjectNormalizer $normalizer
*/
public function __construct(ObjectNormalizer $normalizer)
{
$this->normalizer = $normalizer;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
return $type === PostDTO::class;
}
/**
* #param mixed $data
* #param string $type
* #param string|null $format
* #return array|bool|object
* #throws ExceptionInterface
*/
public function supportsDenormalization($data, string $type, string $format = null)
{
// validate the array which will be normalized (you should write your validator and inject it through the constructor)
if (!is_string($data['name'])) {
// normally you would throw an exception and leverage the `ErrorController` functionality
// do something
}
// convert the array to the object
return $this->normalizer->denormalize($data, $type, $format);
}
}
If you want to access the context array, you can implement the DenormalizerAwareInterface. Normally, you would create your custom validation and inject it into this denormalizer and validate the $data array.
Please not that I have injected the ObjectNormalizer here so that when the data successfully passed the validation, I can still construct the PostDTO using the $data.
PS: in my case, the autowiring has automatically registered my custom denormalizer. If yours is not autowired automatically, go to services.yaml and add the following lines:
App\Serializer\PostDTODeserializer:
tags: ['serializer.normalizer']
(I have tagged the implementation with serializer.normalizer so as it is recognized during the deserialization pipeline)

How to use #Assert\Type("float")?

With Symfony 5, I have entity with #Assert\Type("float") :
/**
* #var float
*
* #ORM\Column(type="decimal", nullable=true, precision=6, scale=3)
* #Assert\Type("float")
* #Assert\PositiveOrZero()
*/
private $premium;
/**
* Get premium
*
* #return float
*/
public function getPremium(): ?float
{
return $this->premium;
}
/**
* Set premium
*
* #param float $premium
*/
public function setPremium(?float $premium): void
{
$this->premium = $premium;
}
I have a form for this entity :
public function buildForm(
FormBuilderInterface $builder,
array $options
): void {
$builder
->add('premium', MoneyType::class, [
'label' => 'label.premium.price',
'currency' => 'EUR',
'required' => false,
])
;
}
But when I validate the form with 9.99, I have this error :
ERROR This value must be of type float.
When I dump($value) in vendor/symfony/validator/Constraints/TypeValidator.php :
"9.990"
I think my form sends a string rather than a float, but I am surprised that Symfony does not manage this situation.
Everything is a string in an HTTP request, data types are meaningful only inside an application.
Anyway, to manage this case you need to attach an implementation of DataTransformerInterface that checks the data (if it's a string, is numeric, etc) and then casts the value to a float number.

FosRest : Date change is impossible with put method

I'm working on a PUT method which allows user to update his profile. My problem is when I try to update the birthDate field, in the user entity:
/**
* #var \DateTime
*
* #ORM\Column(name="birth_date", type="date", nullable=true)
*
* #Expose
*/
private $birthDate;
With getters and setters:
/**
* Set birthDate
*
* #param \DateTime $birthDate
* #return User
*/
public function setBirthDate(\DateTime $birthDate)
{
$this->birthDate = $birthDate;
return $this;
}
/**
* Get birthDate
*
* #return \DateTime
*/
public function getBirthDate()
{
return $this->birthDate;
}
Here is my controller code sample:
$user = $this->getDoctrine()->getRepository('FTUserBundle:User')->find($user_id);
if(null == $user) {
$view = View::create("User not found", Codes::HTTP_NOT_FOUND);
return $this->handleView($view);
}
$form = $this->createForm(new UserProfileType(), $user, array('method' => 'PUT'));
The error is right here, when createForm is called:
{"error":{"code":500,"message":"Internal Server Error","exception":[{"message":"Unable to transform value for property path \"birthDate\": datefmt_format: string '' is not numeric, which would be required for it to be a valid date: U_ILLEGAL_ARGUMENT_ERROR"
Here's my UserProfileType:
$builder->add('birthDate', 'date', array(
'widget' => 'single_text',
'format' => 'dd-MM-yyyy'
));
The birthDate is correctly set in the database. If I replace the return of the birthDate getter by a static DateTime object, it works fine. When I call the $user->getBirthDate(), before building the form, it returns me a correct DateTime object.
My mistake was in my UserProfileType(). The birthDate field type must be "datetime", and not "date".

Symfony2, Doctrine, underscore in "findXxx" method name

Simple example, we've got
/**
* #ORM\Column(name="api_keyID", type="integer", nullable=false)
*/
private $api_keyID;
/**
* #return integer
*/
public function getApi_keyID()
{
return $this->api_keyID;
}
/**
* #param integer $api_keyID
* #return object
*/
public function setApi_keyID($data)
{
$this->api_keyID = $data;
return $this;
}
Look at method name and column name. When i try
//...
->findOneByApi_keyID($some);
I'm getting an error like
Entity 'entity\path' has no field 'apiKeyID'. You can therefore not call 'findOneByApi_keyID' on the entities' repository
So doctrine\symfony eats underscore? О.о And i cannot use it in column name?
is the way out
$repository->findBy(array('is_enabled' => true));
Founded here
Magic Doctrine2 finders when field has underscore?

Create new type in symfony2

How can I create FormType in Symfony2 for converting an entity to a string and back?
I've done all that is saying in here but there is an error:
Expected argument of type "string", "<Vendor>\<Bundle>\Entity\User" given
How can I create a form where a text field will be converted to an user object?
Assuming User has an username field i would do a transform like the following. Please pay attention that transform is for User to string transform, while reverseTransform is the opposite.
Add the transformer to your form field:
$builder
->add('user', 'text')
->addViewTransformer($transformer)
Relevant code (like example you've cited):
/**
* Transforms an User to a string.
*
* #param User|null $user
* #return string
*/
public function transform($user)
{
return $user ? $user->getUsername() : '';
}
/**
* Transforms a string to an User.
*
* #param string $username
* #return User|null
*/
public function reverseTransform($username)
{
if(empty($username)) return null;
$user = $this->om
->getRepository('AcmeHelloBundle:User')
->findOneBy(array('username' => $username))
;
return $user; // Can be null
}
You can extract this form type here and use that. https://github.com/symfony/symfony/pull/1951 it does what you are asking.

Resources