API Request body format accepted by form - rest

I build an API with Symfony, so I create a CRUD for an entity, and I import Nelmio Api Doc Bundle for generate a doc with Swagger.
I build POST action with a form, and in the request body, I need to add products relation to the cart I create. So this is the body request I got in my Swagger documentation.
{
"datetime": "2023-02-03T12:54:05.661Z",
"customer": {},
"products": [
{
"id": 0
}
]
}
But this body don't work, only this format is accepted :
"products": [
2, 4
]
Or this one :
"products": {
"a": 2,
"b": 4
}
Can you help me to find the good configuration in my form, for my form can accept the JSON body format :
"products": [
{
"id": 0
}
]
Cart.php
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $datetime = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
private Customer $customer;
#[ORM\ManyToMany(targetEntity: Product::class)]
private ?Collection $products = null;
(getters and setters...)
CartType.php
->add('products', EntityType::class, [
'class' => Product::class,
'multiple' => true,
'constraints' => [
new NotNull(),
],
])
CartController.php
#[Route('/api/v1/customer/cart', name: 'cart_create', methods: ['POST'])]
public function createAction(EntityManagerInterface $em, Request $request): Response
{
$form = $this->buildForm(CartType::class);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
return $this->respond($form, Response::HTTP_BAD_REQUEST);
}
/** #var Cart $cart */
$cart = $form->getData();
$em->persist($cart);
$em->flush();
return $this->respond($cart);
}

Related

Symfony Form CollectionType with a media field (VichUpload for example)

I've got a problem with my collection of object which have a media field.
Entity Section with a collection of sectionContent:
class Section
{
/**
* #var ArrayCollection|Collection|SectionContent[]
* #ORM\OneToMany(targetEntity="MyBundle\Entity\SectionContent", mappedBy="section", cascade={"persist", "remove"})
* #Groups({"section_full"})
*/
protected Collection $sectionContents;
}
Form :
->add(
'sectionContents',
CollectionType::class, [
'entry_type' => SectionContentType::class,
'required' => false,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'delete_empty' => true,
]
)
SectionContent entity with a picture field
class SectionContent
{
/**
* #Groups({"section_full"})
*
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\PublicMedia", cascade={"persist", "remove"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="public_media_id", referencedColumnName="id")
* })
*
* #var PublicMedia
*/
protected $picture;
}
Form:
->add(
'picture',
SectionPictureType::class,
[
'label' => false,
'required' => false
]
)
PublicMedia entity is a Vich/Uploadable class
/**
* #ORM\MappedSuperclass
* #Vich\Uploadable
*/
abstract class PublicMedia
{
/**
* #Vich\UploadableField(mapping="public_media_uploader", fileNameProperty="filename")
*
* #var File|string
*/
private $file;
}
If I've got an Section like this :
"sections": {
"0": {
"id": 1,
"sectionContents": {
"0": {
"id": 1,
"picture": {
"id": 1,
"uuid": "uuid1",
"filename": "test_1.png"
}
},
"1": {
"id": 2,
"picture": {
"id": 2,
"uuid": "uuid2",
"filename": "test_2.png"
}
}
}
}
},
I've got two pictures test_1 and test_2 saved in a directory on my project (web/upload/section/setion_content for example), but when I delete the first one (test_1), symfony didn't delete the linked picture as I expected.
It update the first section with the data of the second so I've got:
"sections": {
"0": {
"id": 1,
"sectionContents": {
"0": {
"id": 1,
"picture": {
"id": 1,
"uuid": "uuid1",
"filename": "test_2.png"
}
}
}
}
},
As I understand, symfony update the first section data with the second and delete the second, but when it delete the second section, it delete the linked picture as well so I've got a section with test_2 picture on data and test_1 linked picture on the directory. So my sectionContent is linked to a deleted picture and I've got a picture without linked sectionContent.
Before:
[{picture: {filename: test_1}}, {picture: {filename: test_2}}] -> web/upload/.../test_1 and web/upload/.../test_2
After:
[{picture: {filename: test_2}}] -> web/upload/.../test_1
I don't know how to fix that ...
When I delete a sectionContent I want to delete the linked picture and not the last one !
Do you have some ideas please ?

load cities in choiceType after select one gouvernorat in a second ChoiceType

I have two entities with association Gouvernorat -> Villes.
This is the Gouvernorat entity:
class Gouvernorat
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=100, nullable=false)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="EntiteBundle\Entity\Ville", mappedBy="idGouvernorat")
*/
private $villes;
//getters & setters & constructor
}
and this is the City Entity:
class Ville
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=200, nullable=false)
*/
private $name;
/**
* #var \Gouvernorat
*
* #ORM\ManyToOne(targetEntity="EntiteBundle\Entity\Gouvernorat",inversedBy="villes")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_gouvernorat", referencedColumnName="id")
* })
*/
private $idGouvernorat;
}
In a third entity called Etablissement, i have string gouvernorat and string string ville.So i am trying to create an EtablissementType. This form contains
two Entity type for the child gouvernorat and ville
Brief copy :
class EtablissementType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('gouvernorat', EntityType::class, array(
'class' => 'EntiteBundle\Entity\Gouvernorat',
'choice_label' => 'name',
'multiple' => false
))
->add('ville',EntityType::class,
array(
'class' => 'EntiteBundle\Entity\Ville',
'choice_label' => 'name',
'multiple' => false
))
->add('Enregistrer',SubmitType::class)
-> setMethod('POST');
}
when i select gouvernorat i want that all cities of this gouvernorat are loaded i the second ChoiceType so i added this event:
public function buildForm(FormBuilderInterface $builder, array $options)
{
//$builder-> ...
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
$gouv = $data-> getGouvernorat();
$villes = null === $gouv ? array() : $gouv->getVilles();
$names = array_map(function ($value) {
return $value['name'];
}, $villes);
$form->add('ville', ChoiceType::class,
array(
'choices' => $names
));
}
);
}
shortcut for the data(in json forma):
[
{
"id": 1,
"name": "ARIANA",
"villes": [
{
"id": 1,
"name": "RAOUED",
"idGouvernorat": null
},
{
"id": 2,
"name": "SIDI THABET",
"idGouvernorat": null
},
{
"id": 3,
"name": "ARIANA VILLE",
"idGouvernorat": null
},
{
"id": 4,
"name": "LA SOUKRA",
"idGouvernorat": null
},
{
"id": 5,
"name": "KALAAT LANDLOUS",
"idGouvernorat": null
},
{
"id": 6,
"name": "ETTADHAMEN",
"idGouvernorat": null
},
{
"id": 7,
"name": "MNIHLA",
"idGouvernorat": null
}
]
},
{
"id": 2,
"name": "BEJA",
"villes": [
{
"id": 8,
"name": "BEJA NORD",
"idGouvernorat": null
},
{
"id": 9,
"name": "NEFZA",
"idGouvernorat": null
},
{
"id": 10,
"name": "GOUBELLAT",
"idGouvernorat": null
},
{
"id": 11,
"name": "MEJEZ EL BAB",
"idGouvernorat": null
},
{
"id": 12,
"name": "AMDOUN",
"idGouvernorat": null
},
{
"id": 13,
"name": "TEBOURSOUK",
"idGouvernorat": null
},
{
"id": 14,
"name": "TESTOUR",
"idGouvernorat": null
},
{
"id": 15,
"name": "THIBAR",
"idGouvernorat": null
},
{
"id": 16,
"name": "BEJA SUD",
"idGouvernorat": null
}
]
},
etc...
The problem, is that the second choiceType always empty that mean $gouv->getVilles(); doesn't work
Where is the problem, i didn't find the issue exactly , i usedsymfony documentation--Dynamic Generation for Submitted Forms
<script>
var $gouvernorat = $('#etablissement_gouvernorat');
// When gouvernorat gets selected ...
$gouvernorat.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected gouvernorat value.
var data = {};
data[$gouvernorat.attr('name')] = $gouvernorat.val();
// Submit data via AJAX to the form's action path.
$.ajax({
url : $form.attr('action'),
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current ville field ...
$('#etablissement_ville').replaceWith(
// ... with the returned one from the AJAX response.
$(html).find('#etablissement_ville')
);
// Ville field now displays the appropriate Villes.
}
});
});
</script>
First when you get your gouv data in the form, I am not sure if it works as expected.
Replace
$form = $event->getForm();
$data = $event->getData();
$gouv = $data->getGouvernorat();
By:
$form = $event->getForm();
$data = $event->getData();
$gouv = $data->get('gouvernorat')->getData(); // basically, you get the form data for the gouvernorat field
At this moment I think gouvernorat is not populated with its own villes which are stored in the database (I suppose).
Now you will need to check if the getGouvernorat method of your $data (which is an Etablissement instance) returns a Gouvernorat instance.
If yes you will get this gouvernerat from your database, in order to access all its cities.
if (!is_null($data->getGouvernorat())) {
$persistedGouvernorat = $gouvernoratRepository->find($event->getData()->getGouvernorat());;
if (!is_null($persistedGouvernorat)) {
$gouverorat = $persistedGouvernorat;
}
}
$villes = $gouvernorat->getVilles();
Then I think you can get rid your issue.

Setting extraFields in rest api yii2

public function fields()
{
return [
'field' => 'field',
];
}
public function extraFields()
{
return [
'users',
];
}
return:
{ "field": "field", "users": { "id": 1, "name": "user" } }
how to exclude id?
public function extraFields()
{
return [
'users' => function($model){
return [
'name' => $model->users->name,
];
}
];
}
return:
{ "field": "field", "users": { "name": null } }
how to fill in the name field correctly or how to customize field output filtering?
Option can be overwrite field() method in User model:
public function fields()
{
$fields = parent::fields();
if ($something) {
unset($fields['id']);
}
return $fields;
}

400 Bad Request Symfony 3 with Angular

I'm trying to figure out what am I doing wrong. I'm getting 400 bad request trying to send a post request via angular service. I have 2 entities - Document and DocumentCategory (many to many relation). I can post document itself (without categories) with no problem.
document-create.component.ts
createDocument(title, body, categories) {
let document = {title: title, body: body, categories: categories};
this._crudService.createDocument(document).subscribe(
data => {
return true;
},
error => {
console.error("Error saving document! " + error);
return Observable.throw(error);
}
);
}
crudService.ts
createDocument(document) {
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
//let body = JSON.stringify(document);
let body = document;
return this.http.post
('http://localhost:8000/documents', body, headers);
}
The form
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('title', TextType::class)
->add('body', TextType::class)
//->add('categories', TextType::class)
->add('categories', EntityType::class, array(
'class' => 'AppBundle:DocumentCategory',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
'choice_label' => 'id',
))
;
}
Document.php
/**
* #ORM\ManyToMany(targetEntity="DocumentCategory", mappedBy="documents")
* #JMSSerializer\Expose
*/
private $categories;
DocumentCategory.php
/**
* #ORM\ManyToMany(targetEntity="Document", inversedBy="categories")
* #ORM\JoinTable(name="document_category_document")
* #JMSSerializer\Expose
*/
private $documents;
Request
POST /documents HTTP/1.1
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8
Connection: keep-alive
Content-Length: 213
Content-Type: application/json
Host: localhost:8000
Origin: http://localhost:4200
Referer: http://localhost:4200/admin/document/create
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
X-Php-Ob-Level: 1
{
"title": "t",
"body": "<p>b</p>",
"categories": [
{
"id": 1,
"name": "cat1",
"documents": []
},
{
"id": 2,
"name": "cat2",
"documents": []
}
]
}
As I said if I remove categories, everything works. I can't figure it out :(
EDIT: Postman shows this in response when I try to send above json as application/json:
{
"children": {
"title": {},
"body": {},
"categories": {
"errors": [
"This value is not valid."
],
"children": {
"1": {},
"2": {},
"3": {},
"4": {}
}
}
}
}
After looong time I finally made it. Here's my result in case someone has a simmilar problem. It's not the most elegant solution and I will try to find a better one but at least it works. The problem was in controller. Now the post method looks like this:
public function postAction(Request $request) {
$form = $this->createForm(DocumentType::class, null, [
'csrf_protection' => false,
]);
$form->submit($request->request->all());
if (!$form->isValid()) {
return $form;
}
$em = $this->getDoctrine()->getManager();
$document = $form->getData();
$categories = $request->request->get('categories');
foreach ($categories as $categoryId) {
$category = $em->getRepository('AppBundle:DocumentCategory')->find((int)$categoryId['id']);
$category->addDocument($document);
$document->addCategory($category);
$em->persist($category);
}
$em->persist($document);
$em->flush();
$routeOptions = [
'id' => $document->getId(),
'_format' => $request->get('_format'),
];
return $this->routeRedirectView('get_document', $routeOptions, Response::HTTP_CREATED);
}
And my form is simply:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('title', TextType::class)
->add('body', TextType::class)
;
}

Yii2 REST choosing fields in extrafields

From the yii2 definite guide:
public function fields()
{
return ['id', 'email'];
}
public function extraFields()
{
return ['profile'];
}
the request with http://localhost/users?fields=id,email&expand=profile may return the following JSON data:
[
{
"id": 100,
"email": "100#example.com",
"profile": {
"id": 100,
"age": 30,
}
},
...
]
How can I tune extraFields (or maybe something else) to get only one field (for example, age) in profile section of response in this sample?
This feature will be added in Yii 2.0.14
https://github.com/yiisoft/yii2/pull/14219
Example API request will be work like this: users?fields=id,email&expand=profile.age
Edit: For now you can use something like this:
public function extraFields()
{
return [
'profileAge' => function($item){
return $item->profile->age
}
];
}
with request: http://localhost/users?fields=id,email&expand=profileAge
The first thing that came to mind
public function extraFields(){
return [
'profile' => function($item){
return [
'age' => $item->profile->age
];
}
];
}