Symfony API Platform : file upload on update - symfony

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.

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

How to add JSON request and response examples in Swagger (OpenApi)?

I have an API endpoint in my Symfony 4 application, that I want to document with NelmioApiDocBundle and Swagger. The endpoint takes JSON as a request data and also returns some custom JSON as the response. How can I add examples of them to the documentation, using annotations? I can't see any of the examples on the documentation page, only the description.
/**
* #Route("/order/import", methods={"POST"}, name="order_import")
* #OA\RequestBody (
* request="order",
* description="Order data in JSON format",
* #OA\Schema(
* type="object",
* example={"hello": "world"}
* )
* )
* #OA\Response(
* response=200,
* description="Returns the JSON data after import",
* #OA\Schema(
* type="object",
* example={"foo": "bar"}
* )
* )
* #OA\Tag(name="import")
For NelmioApiDocBundle v4 you can do like this
use OpenApi\Annotations as OA;
/**
* #OA\Parameter(
* name="body",
* in="path",
* required=true,
* #OA\JsonContent(
* type="object",
* #OA\Property(property="property1", type="number"),
* #OA\Property(property="property2", type="number"),
* ),
* )
*
* #OA\Response(
* response=200,
* description="",
* #OA\JsonContent(
* type="object",
* #OA\Property(property="property1", type="number"),
* #OA\Property(property="property2", type="number"),
* )
* )
*/
For v3
use Swagger\Annotations as SWG;
/**
* #SWG\Parameter(
* name="body",
* in="body",
* required=true,
* #SWG\Schema(
* #SWG\Property(property="test1", type="string"),
* #SWG\Property(property="test2", type="string"),
* ),
* )
*
* #SWG\Response(
* description="",
* response=200,
* #SWG\Schema(
* #SWG\Property(property="test1", type="string"),
* #SWG\Property(property="test2", type="string"),
* ),
* )
*/
But better to do it via #Model annotation, as described in the doc if you have DTO or entity.

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")
*/

How can I change api-platform {id} to a different name?

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

Resources