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...
Related
How I can make two different manyToMany relations in Symfony between the same entities?
If I create it by entity generator, I am getting the error:
In SchemaException.php line 112:
The table with name db.table1_table2' already exists.
The generator doesn't manage the relation table properties. You have to write it on your own and declare the #JoinTable.
/**
* #var OtherEntity[]
*
* #ORM\ManyToMany(targetEntity="OtherEntity")
* #ORM\JoinTable(
* name="this_entity_other_entity",
* joinColumns={
* #ORM\JoinColumn(name="this_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="other_entity_id", referencedColumnName="id")
* }
* )
*/
private $otherEntities;
I'm using Symfony5 and ApiPlatform
I have a User entity and a Product entity.
I want to list all my user's products through a subressource, to do so I've implemented my user class as follow :
/**
* #ApiResource(
* attributes={
* "normalization_context"={"groups"={"user:read", "user:list"}},
* "denormalization_context"={"groups"={"user:put", "user:post"}}
* },
* subresourceOperations={
* "api_users_consultations_get_subresource"={
* "method"="GET",
* "security"="is_granted('ROLE_ADMIN')"
* }
* },
* collectionOperations={
* "get"={
* "method"="GET",
* "security"="is_granted('ROLE_ADMIN')",
* "normalization_context"={"groups"={"user:list"}}
* },
* "post"={
* "method"="POST",
* "security_post_denormalize"="is_granted('POST', object)",
* "denormalization_context"={"groups"={"user:post"}}
* }
* },
* itemOperations={
* "get"={
* "method"="GET",
* "security"="is_granted('GET', object)",
* "normalization_context"={"groups"={"user:read"}}
* }
* }
* )
* #Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false, hardDelete=false)
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"user:read", "user:list"})
*
*/
private $id;
/**
* #ORM\OneToMany(targetEntity=Product::class, mappedBy="user")
* #ApiSubresource()
*/
private $product;
}
It does create a route /users/{id}/products and return me what I want.
The part where I'm blocking is when I want to add authorization to this route:
ROLE_ADMIN can access this route
ROLE_USER who own the ressource can access it
All the other roles will receive FORBIDDEN
To do so I've followed the doc : https://api-platform.com/docs/core/subresources/#using-serialization-groups
Added subresourceOperations to #ApiSubresource annotation
recovered the name of my grenerated route api_users_consultations_get_subresource through the bin/console debug:router command
and simply set a security=is_granted('ROLE_ADMIN') method like for other operations.
or security=is_granted('SUB_LIST', object) to hit the voter
but when I run my tests I get 200 where I should receive 403, the UserVoter or ProductVoter are not triggered nor the is_granted('ROLE_ADMIN') rule.
As if the subresourceOperations annotation wasn't recognize by the ApiPlatform.
I've also tried changing the name of the operation from api_users_consultations_get_subresource to :
consultations_get_subresource
api_consultations_get_subresource
clients_get_subresource
api_clients_get_subresource
and different other variants as I saw on Github it solved the issue in some cases (like here https://github.com/api-platform/api-platform/issues/1581#issuecomment-662503549) but it has not worked for me.
So I'm wondering is there something I havn't done to implement it correctly ?
Is it a known issue of ApiPlatform ?
Does anyone see where my logic is failing ?
Is there another way to setup security on subresource routes ?
Are there more docs on security realted to subresource? I have not find a lot of material on this particular subject
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"
}
]
}
I am using api-platform with Symfony 4. It works fine, but I would like to change a GET url from:
/booking/{id} to /booking/{bookingId}
I'm using my own DTO object and custom data provider (not Doctrine ORM).
Here are the current #ApiResource and #ApiProperty definitions that work fine:
/**
*
* #ApiResource(
* itemOperations={
* "get"={
* "path"="/booking/{id}",
* },
* "api_bookings_get_item"={
* "swagger_context"={
* "operationId"="getBookingItem",
* "summary"="Retrieves details on a booking",
* "parameters"= {
* {
* "name"="id",
* "description"="Booking ID",
* "default"="15000",
* "in"="path",
* "required"=true,
* "type"="string"
* }
* },
* "responses"={
* "200"={
* "description"="Results retrieved"
* },
* "404"={
* "description"="Booking not found"
* }
* }
* }
* }
* },
* collectionOperations={}
* )
*/
final class Booking
{
/**
* #var string
* #Assert\NotBlank
*
* #ApiProperty(
* identifier=true,
* attributes={
* "swagger_context"={
* "description"="Booking ID",
* "required"=true,
* "type"="string",
* "example"="123456"
* }
* }
* }
*/
public $id;
// other variables
}
However, if I change all the references from 'id' to 'bookingId' it stops working and I get a 404 error. Here are the changes I made to the above code:
"path"="/booking/{bookingId}"
"name"="bookingId"
public $bookingId;
Is api-platform hard-coded to use 'id' as an identifier? Is there any way to change this?
In Api-platform the id parameter is hardcoded:
namespace ApiPlatform\Core\DataProvider;
private function extractIdentifiers(array $parameters, array $attributes)
{
if (isset($attributes['item_operation_name'])) {
if (!isset($parameters['id'])) {
throw new InvalidIdentifierException('Parameter "id" not found');
}
but you can create your own operation and use the parameter name that you want there is a great example in docs custom operations
You can customize the apiResource identifier via:
/**
* #ApiProperty(identifier=false)
*/
private $id;
and:
/**
* #ApiProperty(identifier=true)
*/
private $uuid;
where uuid is the new identifier to be used in request URLs.
For reference: symfonycasts did an excellent tutorial here:
https://symfonycasts.com/screencast/api-platform-extending/uuid-identifier
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/