Subresources routes in symfony 5 with API-platform - symfony

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;
// ...
}

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
* }

Api Platform : order on an entity cascade

With Api Platform, we can order by association.
Documentation :
use ApiPlatform\Core\Annotation\ApiResource;
/**
* #ApiResource(attributes={"order"={"author.username"}})
*/
class Book
{
...
But I want to order on an entity cascade.
For the example below I tested this with this error :
* #ApiResource(
* attributes={
* "order"={"exercises.repetitions.id":"asc"}
* }
* )
[Semantical Error] line 0, col 422 near 'id ASC, rests_a6.id': Error: Class App\Entity\TrainingSerieExercise has no field or association named repetitions.id
TrainingSerie entity :
* #ApiResource(
...
* attributes={
* "order"={"exercises.repetitions.id":"asc"}
* }
* )
*/
class TrainingSerie
{
...
/**
* #var TrainingSerieExercise[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="App\Entity\TrainingSerieExercise",
* mappedBy="serie",
* cascade={"persist"}
* )
* #Groups({
* "trainings_read",
* "training_series_read",
* "training_series_write",
* })
*/
private $exercises;
...
TrainingSerieExercise entity :
...
class TrainingSerieExercise
{
...
/**
* #var TrainingSerieRepetition[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="App\Entity\TrainingSerieExerciseRepetition",
* mappedBy="exercise",
* cascade={"persist"},
* )
* #Groups({
* "training_series_read",
* "training_series_write",
* "training_serie_exercices_write",
* })
*/
private $repetitions;
...
TrainingSerieExerciseRepetition entity :
class TrainingSerieExerciseRepetition
{
...
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
* #Groups({
* "training_series_read",
* })
*/
private $id;
...
Following this Doctrine doc this will order TrainingSerieExercise::$repetitions by id:
class TrainingSerieExercise
{
...
/**
* #var TrainingSerieRepetition[]|ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="App\Entity\TrainingSerieExerciseRepetition",
* mappedBy="exercise",
* cascade={"persist"},
* )
* #ORM\OrderBy({"id" = "ASC"})
* #Groups({
* "training_series_read",
* "training_series_write",
* "training_serie_exercices_write",
* })
*/
private $repetitions;
...

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

Symfony API Platform : file upload on update

I'm stuck with something with API Platform and Vich Uploader for a PUT request, the POST is working juste fine.
Here is my header for MediaObject entity :
/**
* #ORM\Entity
* #ApiResource(
* iri="http://schema.org/MediaObject",
* normalizationContext={
* "groups"={"media_object_read"}
* },
* collectionOperations={
* "post"={
* "controller"=CreateMediaObjectAction::class,
* "deserialize"=false,
* "security"="is_granted('ROLE_USER')",
* "validation_groups"={"Default", "media_object_create"},
* "openapi_context"={
* "requestBody"={
* "content"={
* "multipart/form-data"={
* "schema"={
* "type"="object",
* "properties"={
* "file"={
* "type"="string",
* "format"="binary"
* }
* }
* }
* }
* }
* }
* }
* },
* "get",
* },
* itemOperations={
* "get",
* "put"={"controller"=UpdateMediaObjectAction::class,
* "deserialize"=false,
* "security"="is_granted('ROLE_USER')",
* "validation_groups"={"Default", "media_object_update"},
* "openapi_context"={
* "requestBody"={
* "content"={
* "multipart/form-data"={
* "schema"={
* "type"="object",
* "properties"={
* "file"={
* "type"="string",
* "format"="binary"
* }
* }
* }
* }
* }
* }
* }},
* "delete"={
* "security"="is_granted('ROLE_USER')"
* }
* }
* )
* #Vich\Uploadable
*/
class MediaObject
{...}
In swagger it throws an error because the file is not attached to the request. It strange because I have exactly the same file input field, with just an ID parameter added.
Someone has manage to do that ?
Found solution by myself !
Finally, the problem is that PHP does'nt handle well file in FormData on PUT, but well on POST.
So if you got the same issue change to "method"="POST" in the "put" part from itemOperations.

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