symfony FOSRestBundle depth - symfony

I'm using FOSRestBundle - with automatic rotues and automatic views. My action in Controller looks like this:
public function getAction($user_id)
{
$user = $this->em->getRepository('SBGUserBundle:User')->find($user_id);
return $user;
}
Everything is OK, my response in JSON format looks like this:
{
"id": 20,
"username": "fwojciechowski",
"mpks": [{
"id": 91,
"name": "Testowe MPK 1",
"managers": []
}, {
"id": 92,
"name": "Testowe MPK 2",
"teta_id": 1,
"managers": []
}]
}
But I have to take 1 level depth more - i need "managers" in "mpks" array.
But I don't need 3 levels in other cases.
How can I do it?

annotations
if you are using annotations you can do it like this yml configs later for reference.
first you go to the manager entity and add the following :
/**
* Manager
*
* #ORM\Table(name="manager")
* #ORM\Entity
*
* #Serializer\ExclusionPolicy("all")
*/
class Manager
{
......
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
* #Serializer\Expose
* #Serializer\Groups({
* "user",
* })
*/
private $id;
now you decide what properties you need from the managers and add the following before each entity
* #Serializer\Expose
* #Serializer\Groups({
* "user",
* })
$property
then before your getAction you add this
/**
* Get User.
*
* #param User $user
*
* #return User
*
* #Route\Get("/users", options={"expose"=true})
*
* #View(serializerGroups={"user"}) \\notice that its the same group name from before Serializer\Groups({"user"})
*
* #ApiDoc(
* ....
* )
*/
public function getAction()
yml
it should look something like this
Acme\User:
exclusion_policy: ALL
properties:
id:
expose: true
username:
expose: true
mpks:
expose: true
Acme\Manager
exclusion_policy: ALL
properties:
id:
expose: true
first_name:
expose: true
last_name:
expose: true
#and the rest of your wanted properties

Related

Symfony same #groups between two table with ManyToMany relations

I want to have the same #groups between two table with ManyToMany relations: When i get on API Platform .../api/tags/1, I recieve only that without "tag".
{
"id": 1,
"title": "A ce monde que tu fais"
}
App\Entity\Song
/**
* #Groups({"song:read", "song:write"})
* #ORM\ManyToMany(targetEntity=Tag::class, inversedBy="songs", cascade={"persist"})
* #ORM\JoinTable(
* name="song_tag",
* joinColumns={
* #ORM\JoinColumn(name="song_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* })
*
*/
private $tags;
App\Entity\Tag
/**
* #Groups({"song:read", "song:write"})
* #ORM\ManyToMany(targetEntity=Song::class, mappedBy="tags")
*/
private $songs;
I think it's the join table between the two, it doesn't have a defined group. Can you help me?
Thanks
What do you mean by:
When i get on API Platform .../api/tags/1, I recieve only that without "tag".
From what I understand, your problem is likely the result of missing normalization context configuration. Tag endpoints, such as /api/tags/1, are not configured to apply the song:read and song:write serialization groups by default, resulting in these Tag fields being excluded in the response payload.
Consider adding song:read and song:write as default serialization groups for the Tag endpoints. Or preferably, specify tag:read and tag:write serialization groups and add them to the Song serialization groups. A simple example:
<?php declare(strict_types = 1);
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ApiResource(
* itemOperations={
* "get"={
* "normalization_context"={
* "groups"={
* "song:read",
* },
* },
* },
* },
* )
*/
class Song
{
/**
* #Groups({
* "song:read",
* "tag:read",
* })
*/
private Collection $tags;
}
/**
* #ORM\Entity
* #ApiResource(
* itemOperations={
* "get"={
* "normalization_context"={
* "groups"={
* "tag:read",
* },
* },
* },
* },
* )
*/
class Tag
{
/**
* #Groups({
* "tag:read",
* "song:read",
* })
*/
private Collection $songs;
}
PS: Consider that the above example establishes a circular reference.
by
When i get on API Platform .../api/tags/1, I recieve only that without "tag".
I mean that when I make a request, I want to get the tags back. I've already tried it and it works with the oneToMany and the oneToOne but not for the ManyToMany.
I want
{
"id": 1,
"title": "world",
"category": {"id":54, "name":"hello"},
"tags": [{"id":12, "name":"city"}, {...}]
}
and I have only
{
"id": 1,
"title": "world",
"category": {"id":54, "name":"hello"},
}
I think it's really the join table between the two, it doesn't have a defined group because symfony did not create an entity and therefore no group...

How to update a entry from ArrayCollection on Api-Platform?

With Symfony 5, Api Platform and Doctrine, I want to update a entity and his array collection linked.
When I send PUT request to Api Platform the main entity (UserMeasurement #17) is updated but not the child entity (UserMeasurementMeasurement #28) : a new entry is incremented and created.
PUT request : ​/api​/user_measurements​/17
{
"date": "2020-10-20T12:11:22.609Z",
"measurements": [
{
"id": "/api/user_measurement_measurements/28", // same result with "28" value
"value": "7"
}
]
}
(when creating POST the datas everything works fine (main entity and child entity are created))
Part of main entity :
* #ApiResource(
* collectionOperations={"get", "post"},
* itemOperations={"get", "put", "delete"},
* normalizationContext={
* "groups"={"user_measurements_read"}
* },
* denormalizationContext={
* "groups"={"user_measurements_write"}
* },
* )
class UserMeasurement
{
...
/**
* #var UserMeasurementMeasurement[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="App\Entity\UserMeasurementMeasurement",
* mappedBy="userMeasurement",
* cascade={"persist"},
* )
* #Groups({
* "user_measurements_read",
* "user_measurements_write",
* })
*/
private $measurements;
...
Part of child entity :
* #ApiResource(
* collectionOperations={"get", "post"},
* itemOperations={"get", "put", "delete"},
* normalizationContext={
* "skip_null_values"=false,
* "groups"={"user_measurement_measurements_read"}
* },
* )
class UserMeasurementMeasurement
{
...
/**
* #var float
*
* #ORM\Column(type="decimal", nullable=true, precision=6, scale=2)
* #Assert\Positive()
* #Groups({
* "user_measurements_read",
* "user_measurements_write",
* })
*/
private $value;
...
When using application/ld+json, you must provide the #id property:
PUT request : ​/api​/user_measurements​/17
{
"date": "2020-10-20T12:11:22.609Z",
"measurements": [
{
"#id": "/api/user_measurement_measurements/28",
"value": "7"
}
]
}

API not returning auto generated ID

Am working on Symfony API Platform, to add and retrieve something. Table has two fields id and title.
But when i run the GET query the API is returning title only not id.
How to return ID too?
My Annotation:-
* #ORM\Table(
* name="school",
* #ApiResource(
* attributes={
* "order"={"title": "ASC"},
* "normalization_context"={"groups"={"school.read"},
"enable_max_depth"=true},
* },
* itemOperations={
* "get",
* "put"
* },
* collectionOperations={
* "get"={
* "normalization_context"={
* "groups"={"school.read"}
* }
* }
* },
* normalizationContext={
* "groups"={"school.read"}
* },
* denormalizationContext={
* "groups"={"school.write"}
* }
* )
* #ORM\Entity(repositoryClass="Eqsgroup\Repository\SchoolRepository")
* #UniqueEntity(
* "title",
* repositoryMethod="findByUniqueCriteria",
* message="School already exists."
* )
*/
This is the Entity class
class School
{
/**
* #var string the id of this School
*
* #ORM\Id
* #ORM\Column(type="guid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="Ramsey\Uuid\Doctrine\UuidGenerator")
* #Groups({"school.read, school.write"})
*/
private $id;
/**
* #var string The title of the school
*
* #ORM\Column(type="string", length=255)
* #Assert\NotNull(message="school should not be empty")
* #Assert\NotBlank(message="school should not be empty")
* #Assert\Length(
* min = 1,
* max = 250,
* minMessage = "length.min,{{ limit }}",
* maxMessage = "length.max,{{ limit }}"
* )
* #Groups({"school.read", "school.write"})
*/
private $title;
public function __construct(){ }
public function getId(): ?string
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
This is the output am currently getting:
[
{
"title": "Test"
},
{
"title": "Test2"
},
]
The expected output will include the auto generated is along with title.
Add the #Groups to allow Apli-Platform to read each field you want like this :
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"school.read", "school.write"})
*/
private $id;
Have a look to the documentation here :
https://api-platform.com/docs/core/serialization/#the-serialization-context-groups-and-relations
Did you configure which attributes you want to expose ?
If not, configure it in the yaml of your entity :
# I don't know the path to your entity, just modify what you need to
XXXX\XXXX\XXXX\XXXX\School:
exclusion_policy: ALL
properties:
id:
expose: true
title:
expose: true

FOSRestBundle not use JMSSerializerBundle

I have configured Symfony 3 to auto serialize my action. It works, but the relations aren't serialized:
0
id 1
name "Liste de course"
creation_date "2017-07-07T00:00:00+00:00"
last_update_date "2017-07-07T20:57:06+00:00"
user_entity
_todo_entity_entities
_parent_entity
1
id 2
name "domotique"
creation_date "2017-07-07T00:00:00+00:00"
last_update_date "2017-07-07T21:22:52+00:00"
user_entity
_todo_entity_entities
_parent_entity
If I explicitly use JMSSerializerBundle, it works (user_entity is an object):
0
id 1
name "Liste de course"
creation_date "2017-07-07T00:00:00+00:00"
last_update_date "2017-07-07T20:57:06+00:00"
user_entity Object
_todo_entity_entities
1
id 2
name "domotique"
creation_date "2017-07-07T00:00:00+00:00"
last_update_date "2017-07-07T21:22:52+00:00"
user_entity Object
_todo_entity_entities
I think FOSRestBundle uses the default seralizer, not JMSSerializerBundle:
/**
* #Rest\Get("/projects")
* #View(
* serializerGroups = {"all"}
* )
*/
public function getProjectsAction()
{
$projectEntity = $this->getDoctrine()->getRepository('todoListAdminBundle:Project');
$projects = $projectEntity->findAll();
/*
$data = $this->get('jms_serializer')->serialize($projects, 'json');
// this is work !
$response = new Response($data);
$response->headers->set('Content-Type', 'application/json');
return $response;
*/
return $projects;
}
The entity I serialize :
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="todoListAdminBundle\Repository\ProjectRepository")
*/
class Project
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Serializer\Groups({"all"})
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #Assert\Length(max=50)
* #Assert\Type(type="string")
* #Serializer\Groups({"all"})
*/
private $name;
/**
* #var \DateTime
*
* #ORM\Column(name="creation_date", type="date")
* #Assert\DateTime()
* #Serializer\Groups({"all"})
*/
private $creationDate;
/**
* #var \DateTime
*
* #ORM\Column(name="last_update_date", type="datetime")
* #Assert\DateTime()
* #Serializer\Groups({"all"})
*/
private $lastUpdateDate;
/**
*
* #ORM\ManyToOne(targetEntity="PW\UserBundle\Entity\User", inversedBy="projectEntities" , cascade={"persist"}, inversedBy="projectEntities")
* #Assert\Type(type="integer")
* #Serializer\Groups({"all"})
*/
private $userEntity;
/**
* #ORM\OneToMany(targetEntity="todoListAdminBundle\Entity\TodoEntity", mappedBy="projectEntity", fetch="EAGER")
* #Serializer\Groups({"all"})
*/
private $TodoEntityEntities;
/**
* #var int
*
* #ORM\JoinColumn(nullable=true, referencedColumnName="id")
* #ORM\OneToOne(targetEntity="todoListAdminBundle\Entity\Project")
* #Assert\Type(type="integer")
* #Serializer\Groups({"all"})
*/
private $ParentEntity;
My configuration :
fos_rest:
param_fetcher_listener: true
body_listener: true
zone:
- { path: ^/api/* }
body_converter:
enabled: true
view:
formats: { json: true, xml: false, rss: false }
view_response_listener: true
serializer:
serialize_null: true
format_listener:
enabled: true
rules:
- { path: '^/api', priorities: ['json'], fallback_format: 'json' }
routing_loader:
default_format: json
sensio_framework_extra:
view: { annotations: true }
How can I use JMSSerializerBundle automatically ?
First of all, you need to configure JMSSerializer in your config.yml like:
jms_serializer:
metadata:
cache: file
debug: "%kernel.debug%"
file_cache:
dir: "%kernel.cache_dir%/serializer"
auto_detection: true
Then, create directory with serializer for the given entity YourBundleName/Resources/config/serializer/Entity.Project.yml with this code:
YourBundleName\Entity\Project:
exclusion_policy: ALL
properties:
id:
expose: true
name:
expose: true
"exclusion_policy: ALL" - exclude all the fields from the serialized result. And then you add needed fields with "expose: true". Just do not add "ParentEntity" there and you will not see it in the serialized data (also, I do not think, that mix of camel and pascal case is a good practice, but it's the question of taste).

POST request on many-to-many association

I have two entities with a many-to-many association:
class User extends BaseUser
and
class Calendar
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="text")
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="view", type="text")
* #Assert\Choice(choices = {"work week", "week", "month", "year"}, message = "Choose a valid view.")
*/
private $view;
/**
* #ManyToMany(targetEntity="AppBundle\Entity\User")
* #JoinTable(name="calendars_publishers",
* joinColumns={#JoinColumn(name="calendar_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="publisher_id", referencedColumnName="id", unique=true)}
* )
*/
private $publishers;
public function __construct()
{
$this->publishers = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add publishers
*
* #param \AppBundle\Entity\User $publishers
* #return Calendar
*/
public function addPublisher(\AppBundle\Entity\User $publishers)
{
$this->publishers[] = $publishers;
return $this;
}
/**
* Remove publishers
*
* #param \AppBundle\Entity\User $publishers
*/
public function removePublisher(\AppBundle\Entity\User $publishers)
{
$this->publishers->removeElement($publishers);
}
}
And when I do a POST request on my REST API (http://localhost:8000/calendars) with the body:
{
"name": "My calendar",
"view": "week",
"publishers": [
"/users/1",
"/users/2"
]
}
I have the response:
{
"#context": "/contexts/Calendar",
"#id": "/calendars/3",
"#type": "Calendar",
"name": "My calendar",
"view": "week",
"publishers": []
}
So, as you see, my object is recorded well, but I cannot put some users in publishers. Do you have an idea?
I use the bundles:
DunglasApiBundle
NelmioApiDocBundle
NelmioCorsBundle
FOSHttpCacheBundle
FOSUserBundle
LexikJWTAuthenticationBundle
with api-platform.
You should add addPublisher(User $publisher) and removePublisher(User $publisher) methods.
API Platform internally uses the Symfony PropertyAccess component, and this component requires such methods to be able to access to the private property.
It sounds like you should specify fetch="EAGER" in the relationship so that it always gets the related objects.
see http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/annotations-reference.html#manytoone
(and also the next paragraph)
and some good info here: http://www.uvd.co.uk/blog/some-doctrine-2-best-practices/

Resources