I do not manage to create a custom route for a collection, my entity is named File.
here is my Entity annotation :
/**
* #ApiResource(
*
* normalizationContext={"groups"={"file"},"enable_max_depth"=true},
* denormalizationContext={"groups"={"file-write-customers"},"enable_max_depth"=true},
* attributes={"force_eager"=false},
* itemOperations={
* "get",
* "put",
* "get_mandate_pdf"={
* "method"="POST",
* "path"="/files/{id}/mandate-pdf",
* "controller"=FileCreatePdfController::class,
* },
* },
* collectionOperations={
* "stats"={
* "method"="GET",
* "path"="/files/stats",
* "controller"=FileStatsController::class,
* }
* },
* )
* #ApiFilter(SearchFilter::class, properties={"status": "exact", "sponsor": "exact"})
* #ApiFilter(DateFilter::class, properties={"updatedAt"})
* #ORM\Entity
* #ORM\Table(name="cases")
*/
The controller file
<?php
namespace App\Controller;
use App\Entity\File;
class FileStatsController
{
public function __invoke(File $data): File
{
return $data;
}
}
however i have this error when i reach /files/stats, it seems that api plaform is expecting an Id .
For some reasons if i switch the method from GET to POST the route is working
{
"#context": "\/contexts\/Error",
"#type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "The identifier id is missing for a query of App\\Entity\\File",
"trace": [
{
"namespace": "",
"short_class": "",
"class": "",
"type": "",
"function": "",
"file": "\/srv\/api\/vendor\/doctrine\/orm\/lib\/Doctrine\/ORM\/ORMException.php",
"line": 309,
"args": []
},
I manage to find the solution , in my Controller if i remove the typed variable $data
namespace App\Controller;
class FileStatsController
{
public function __invoke($data)
{
return $data;
}
}
I manage to properly retreive the data
in addition, the method get is mandatory in the ApiRessource annotations
collectionOperations={
"get",
* "stats"={
* "method"="GET",
* "path"="/stats",
* "controller"=FileStatsController::class,
* }
* }
Related
I am having a json request
{
applicant_info:{
"email": "jhon.doe#gmail.com",
"given_name": "jhon",
"family_name": "doe"
},
productId: 12
}
Having a dto class as below:
class Dto
{
/**
* #var int
* #SerializedName("product_id")
*/
public $productId;
/**
* #var Collection
* #SerializedName("applicant_info")
*/
public $applicantInfo = [
'email',
'givenName',
'familyName'
];
}
Now when I use below to deserialize I am not sure how to convert given_name and family_name from request to
givenName and familyName like I have converted product_id and applicant_info to productId and applicantInfo using #SerializedName
$serializer->deserialize(
$request->getContent(),
Dto::class,
self::FORMAT_JSON
);
Any help will be really appreciated
"nelmio/api-doc-bundle": "^3.6#dev"
I need response with model with property product which eque one model Product and property count, but when I use Mode annotation I faced with probem, response model generated like array Product. What I'm doing wrong ?
* #SWG\Response(
* response=200,
* description="Json collection object Products",
* #SWG\Schema(
* type="object",
* properties={
* #SWG\Property(property="product",
* #Model(type=Product::class, groups={Product::SERIALIZED_GROUP_LIST})),
* #SWG\Property(property="count", type="integer")
* }
* )
* )
{
"product": [
{
"id": 0
}
],
"count": 0
}
I expected result like this:
{
"product":
{
"id": 0
},
"count": 0
}
I have currently this entity and I want to show my property firedDate in my JSON even is the value is null.
/**
* #ApiResource(normalizationContext={"groups"={"employee"}})
* #ApiFilter(DateFilter::class, properties={"dateProperty": DateFilter::INCLUDE_NULL_BEFORE_AND_AFTER})
* #ORM\Table(name="employee")
*/
class Employee
{
// ...
/**
* #ORM\Column(type="datetime", nullable=true)
* #Groups({"employee"})
*/
private $firedDate;
public function getFiredDate(): ?\DateTimeInterface
{
return $this->firedDate;
}
// ...
}
Currently, when the value is null, it's not shown in the response.
I think I found the right solution to this problem.
Set skip_null_values in false in your normalizationContext:
* #ApiResource(
* itemOperations={
* "get" = {
* //...
* }
* "put" = {
* //...
* },
* "patch" = {
* //...
* }
* },
* collectionOperations={
* "get",
* "post" = {
* //...
* }
* },
* normalizationContext={
* "skip_null_values" = false,
* "groups" = {"object:read"}
* },
* denormalizationContext={"groups" = {"object:write"}}
* )
Are you under PHP 7.0 or above?
In PHP 7.1 you can have nullable return types for functions, so your
public function getFiredDate(): ?\DateTime
{
return $this->firedDate;
}
With the ? before \DateTime, the function will return null as well.
On ApiPlatform 3 the default has changed from skip_null_values=false to skip_null_values=true.
If you don't want having to set this on each resource, and would like to have the default as it as on ApiPlatform < 3, you can simply set it on the global config:
api_platform:
defaults:
normalization_context:
skip_null_values: false
Or if you use PHP based configuration:
return static function (Symfony\Config\ApiPlatformConfig $apiConfig): void {
$apiConfig
->defaults()
->normalizationContext(['skip_null_values' => false]);
}
Maybe your entity is missing a getter like this one?
public function getFiredDate(): \DateTime
{
return $this->firedDate;
}
Just get the solution from a friend on github, here is it:
* #ApiResource(
* itemOperations={"get"},
* )
BEFORE:
{
"#context": "/contexts/Employee",
"#id": "/employees/1",
"#type": "Employee",
"id": 1,
"name": "Oliver",
"hired": "2019-10-10T00:00:00+00:00",
"experience": 0,
"salary": "1200.00",
"job": {
"#id": "/employee_jobs/1",
"#type": "EmployeeJob",
"id": 1,
"name": "Mécanicien"
}
}
AFTER:
{
"#context": "/contexts/Employee",
"#id": "/employees/1",
"#type": "Employee",
"id": 1,
"name": "Oliver",
"hired": "2019-10-10T00:00:00+00:00",
"experience": 0,
"salary": "1200.00",
"firedDate": null,
"job": {
"#id": "/employee_jobs/1",
"#type": "EmployeeJob",
"id": 1,
"name": "Mécanicien"
}
}
I try to build an API with the Symfony bundle API-Platform.
Api resource offer automatic CRUD action with the HTTP verbs POST, GET, PUT, DELETE.
What I want is adding an endpoint to handle a custom POST action, with a custom payload/body, not depending on any resource.
Where I block it is to add this endpoint to the automatic API-Platform documentation.
When looking for this kind of issue on GitHub, I found that the API-Platform v2 should be able to do it.
See Issue #385 : Custom action + ApiDoc
It looks like some people find the way to use NelmioApiDoc #ApiDoc annotation.
See Issue #17 : Documentation for custom operation
Using the #ApiDoc annotation is a no go, support for NelmioApiDoc will be removed in API Platform 3 in favor of the builtin Swagger/Hydra support.
If you use a custom API Platform action, the action should automatically be documented in Swagger and Hydra docs.
Anyway, you can always customize the Swagger (and Hydra) docs to add custom endpoints or anything else: https://github.com/api-platform/docs/blob/master/core/swagger.md#override-swagger-documentation (this documentation will be available on the website soon).
You can document your own route with the #ApiResource() annotation:
/**
* #ORM\Entity
* #ApiResource(
* itemOperations={
* "get"={"method"="GET"},
* "put"={"method"="PUT"},
* "delete"={"method"="DELETE"},
* "send_reset_password_token"={
* "route_name"="user_send_reset_password_token",
* "swagger_context" = {
* "parameters" = {
* {
* "name" = "email",
* "in" = "path",
* "required" = "true",
* "type" = "string"
* }
* },
* "responses" = {
* "201" = {
* "description" = "email with reset token has been sent",
* "schema" = {
* "type" = "object",
* "required" = {
* "email"
* },
* "properties" = {
* "email" = {
* "type" = "string"
* },
* "fullname" = {
* "type" = "string"
* }
* }
* }
* },
* "400" = {
* "description" = "Invalid input"
* },
* "404" = {
* "description" = "resource not found"
* }
* },
* "summary" = "Send email with token to reset password",
* "consumes" = {
* "application/json",
* "text/html",
* },
* "produces" = {
* "application/json"
* }
* }
* }
* },
* attributes={
* "normalization_context"={"groups"={"user", "user-read"}},
* "denormalization_context"={"groups"={"user", "user-write"}}
* }
* )
*/
Source: https://github.com/api-platform/docs/issues/143#issuecomment-260221717
You can create custom post action like this.
Map resources configuration to yaml.
# config/packages/api_platform.yaml
api_platform:
enable_swagger_ui: false
mapping:
paths: ['%kernel.project_dir%/config/api_platform']
Create resources.yaml
# config/api_platform/resources.yaml
resources:
App\Entity\User:
itemOperations: []
collectionOperations:
post:
method: 'POST'
path: '/auth'
controller: App\Controller\AuthController
swagger_context:
summary: your desc
description: your desc
Then in App\Entity\User add public properties
class User {
public $login
public $password
}
It is all, now in swagger ui u will see method POST /api/auth with login and pass parameters.
In u controller override _invoke for execute your logic.
class AuthController {
public function __invoke()
{
return ['your custom answer'];
}
}
I ran into the same situation because I tried to put the POST method into itemOperations, although it can only reside in collectionOperations. In the latter in can successfully define my custom path.
/**
* #ApiResource(
* collectionOperations={
* "get"={
* "path"="/name_your_route",
* },
* "post"={
* "path"="/name_your_route",
* },
* },
* itemOperations={
* "get"={
* "path"="/name_your_route/group/{groupId}/user/{userId}",
* "requirements"={"groupId"="\d+", "userId"="\d+"},
* },
* "delete"={
* "path"="/name_your_route/group/{groupId}/user/{userId}",
* },
* "put"={
* "path"="/name_your_route/group/{groupId}/user/{userId}",
* }
* })
Hopefully helpful for others that stumble upon this question.
And here is the paragraph from the great documentation about it:
Collection operations act on a collection of resources. By default two
routes are implemented: POST and GET. Item operations act on an
individual resource. 3 default routes are defined GET, PUT and DELETE
Here is a User entity I want to GET with the Hateoas library:
/**
* [...]
*
* #Hateoas\Relation(
* "self",
* href=#Hateoas\Route("api_get_user", parameters={"id"="expr(object.getId())"})
* )
* #Hateoas\Relation(
* "placeofbirth",
* href="expr(link(object.getPlaceOfBirth(), 'self'))",
* exclusion=#Hateoas\Exclusion(excludeIf="expr(object.getPlaceOfBirth() === null)")
* )
*/
class User
{
// [...]
/**
* #ORM\OneToOne(targetEntity="Place")
* #Serializer\Exclude
*/
private $placeOfBirth;
// [...]
}
My REST Response is correct. The _links part expectedly contains a URI for my OneToOne relation:
{
// [...]
"_links": {
"self": {
"href": "\/api\/v0.1\/users\/1"
},
"placeofbirth": {
"href":"\/api\/v0.1\/places\/3"
}
}
}
However, I don't know how to get the same with a OneToMany relation. For example:
/**
* #ORM\OneToMany(targetEntity="Place", mappedBy="visitor")
* #Serializer\Exclude
*/
private $visitedPlaces;
I would like to get (something like):
{
// [...]
"_links": {
"self": {
"href": "\/api\/v0.1\/users\/1"
},
"visitedPlaces": [
{ "href": "\/api\/v0.1\/place\/1" },
{ "href": "\/api\/v0.1\/place\/2" },
{ "href": "\/api\/v0.1\/place\/3" },
{ "href": "\/api\/v0.1\/place\/4" }
]
}
}
I know I can get the visitedPlaces as embedded resources, and it works, but this is not what I want. I don't want to get the whole object, only the resource URI, just as I do with a single-resource relation (see place of birth).
Is it possible, and if so how?