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

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"
}
]
}

Related

How to enable pagination for Sub Resource?

In my API config file, I disabled pagination :
api_platform.yaml :
api_platform:
collection:
pagination:
enabled: false
But I want to enable pagination for a specific subresource :
Parent class :
/**
* #ORM\Entity(repositoryClass="App\Repository\ForumRepository")
* #ApiResource(
* collectionOperations={
* "get",
* },
* itemOperations={"get"},
* )
*/
class Forum
{
...
/**
* #var ForumSubject[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="App\Entity\ForumSubject",
* mappedBy="forum",
* )
* #ApiSubresource(maxDepth=1)
*/
private $subjects;
...
Children class :
/**
* #ORM\Entity(repositoryClass="App\Repository\ForumSubjectRepository")
* #ApiResource(
* collectionOperations={
* "post",
* },
* itemOperations={
* "get",
* "put",
* "delete"
* },
* )
*/
class ForumSubject
{
...
Everything works fine I can access my sub-resources :
/api/forums/a21cb5db-aed7-45a6-884f-3d3e6d7abd8c/subjects
(Return all subjects of forum, route name : api_forums_subjects_get_subresource ).
But I am unable to enable paging on this route, the documentation does not mention anything.
After research I tried this, but nothing works :
Does not work in parent or children class :
* #ApiResource(
* ...
* subresourceOperations={
* "api_forums_subjects_get_subresource"={"pagination_enabled"=true}
* }
Does not work in parent class ; error un children class (The operation "api_forums_subjects_get_subresource" cannot be found in the Swagger specification).
* #ApiResource(
* ...
* collectionOperations={
* "get",
* "api_forums_subjects_get_subresource"={"pagination_enabled"=true}
* },
After looking in the API Platform source code, here is the solution :
In children class :
* #ApiResource(
* ...
* collectionOperations={
* ...
* "api_forums_subjects_get_subresource"={
* "method"="get", // don't forget : otherwise trigger a strange swagger error
* "pagination_enabled"=true
* }

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...

Subresources routes in symfony 5 with API-platform

I am trying to declare a subresource in my symfony app. I followed the api-platform doc about it: https://api-platform.com/docs/core/subresources/
The subresource does appear in the routes, but not under its parent resource.
The routes I currently have:
api_files_get_collection GET ANY ANY /api/files.{_format}
api_files_post_collection POST ANY ANY /api/files.{_format}
api_files_get_item GET ANY ANY /api/files/{id}.{_format}
api_files_patch_item PATCH ANY ANY /api/files/{id}.{_format}
api_files_put_item PUT ANY ANY /api/files/{id}.{_format}
api_external_subscription_requests_get_collection GET ANY ANY /api/external_subscription_requests.{_format}
api_external_subscription_requests_post_publication_collection POST ANY ANY /api/subscribe
api_external_subscription_requests_get_item GET ANY ANY /api/external_subscription_requests/{id}.{_format}
The routes I would like to have:
api_files_get_collection GET ANY ANY /api/files.{_format}
api_files_post_collection POST ANY ANY /api/files.{_format}
api_files_get_item GET ANY ANY /api/files/{id}.{_format}
api_files_patch_item PATCH ANY ANY /api/files/{id}.{_format}
api_files_put_item PUT ANY ANY /api/files/{id}.{_format}
api_files_external_subscription_requests_get_collection GET ANY ANY /api/files/{id}/external_subscription_requests.{_format}
api_files_external_subscription_requests_post_publication_collection POST ANY ANY /api/files/{id}/subscribe
api_files_external_subscription_requests_get_item GET ANY ANY /api/files/{id}/external_subscription_requests/{id}.{_format}
Code in App\Entity\File.php:
/**
* #ORM\Entity
* #ApiResource(
* attributes={"order"={"createdAt": "DESC"}},
* collectionOperations={
* "get"={
* "normalization_context"={"groups"={"model:timestampable", "file:collection:read"}},
* },
* "post",
* },
* itemOperations={
* "get"={
* "normalization_context"={"groups"={"model:timestampable", "file:item:read"}},
* },
* "patch",
* "put"
* },
* )
*/
class File
{
// ...
/**
* #ORM\OneToMany(targetEntity="App\Entity\ExternalSubscriptionRequest", cascade={"all"}, mappedBy="file")
* #Groups({
* "file:collection:read",
* "file:item:read"
* })
* #ApiSubresource()
*/
private $external_subscription_requests;
// ...
}
Code in App\Entity\ExternalSubscriptionRequest.php:
/**
* #ORM\Entity
* #ApiResource(
* collectionOperations={
* "get",
* "post_publication"={
* "method"="POST",
* "path"="/subscribe",
* "controller"=SubscribeToConso::class,
* }
* },
* itemOperations={
* "get",
* },
* )
*/
class ExternalSubscriptionRequest
{
// ...
/**
* #var File the file this request was made for
*
* #ORM\ManyToOne(targetEntity="App\Entity\File", inversedBy="external_subscription_requests")
* #ORM\JoinColumn(referencedColumnName="id", nullable=false)
*
* #Groups({
* "external_subscription_request:collection:read",
* "external_subscription_request:item:read"
* })
*/
public $file;
// ...
}

Api Platform - Use a specific search filter for a specific path

For the same entity, I created 2 different path for inbound and outbound links in my example. In the documentation (Swagger) I want to use as filter only href for inbound and page for outbound. For the moment, all fields appear for the 2 paths.
I'm on Symfony 4.4 and Api Platform 2.4.
Thanks for your help.
/**
* #ApiResource(
* shortName="page_links",
* collectionOperations={
* "inbound"={
* "path"="/page/links/inbound",
* "method"="GET",
* },
* "outbound"={
* "path"="/page/links/outbound",
* "method"="GET",
* }
* },
* itemOperations={"get"={"method"="GET", "path"="/page/links/{id}"}},
* )
* #ApiFilter(SearchFilter::class, properties={"href": "partial", "uid": "partial", "page": "partial"})
* #ORM\Table("res_link_pages")
* #ORM\Entity(repositoryClass="App\Repository\ResLinkPagesRepository")
*/
I found a solution by adding some openapi_context attributes
/**
* #ApiResource(
* shortName="page_links",
* collectionOperations={
* "inbound"={
* "path"="/page/links/inbound",
* "method"="GET",
* "openapi_context" = {
* "parameters" = {
* {
* "name" = "page",
* "type" = "string",
* "in" = "query"
* }
* }
* }
* },
* "outbound"={
* "path"="/page/links/outbound",
* "method"="GET",
* "openapi_context" = {
* "parameters" = {
* {
* "name" = "href",
* "type" = "string",
* "in" = "query"
* }
* }
* }
* }
* },
* itemOperations={"get"={"method"="GET", "path"="/page/links/{id}"}},
* )
* #ApiFilter(SearchFilter::class, properties={"href": "partial", "uid": "partial", "page": "partial"})
* #ORM\Table("res_link_pages")
* #ORM\Entity(repositoryClass="App\Repository\ResLinkPagesRepository")
*/

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

Resources