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?
Related
I am trying to add annotations for a model which has references of child nodes. But those child nodes are being called strings. The same code works just fine if it is referencing a different class. Is there any way this can be accomplished?
class Foo
{
private int $id;
/**
* #OA\Property(
* type="array",
* #OA\Items(
* ref=#Model(type=Foo::class),
* )
* ),
* #var array<Foo>
*/
private array $children;
}
Result
{
"id": 0,
"children": "string"
}
Expected
{
"id": 0,
"children": [{
"id": 0,
"children": [{}]
}]
}
I'm working on some API Platform project.
I 'm using my API point /references to get some data containing subentities.
But the issue is that I use a Doctrine extension to filter my list. Using this extension I can filter which references I get.
But in the subentity Stock I stil have some data that I dont want.
My goal is to get all stocks that have the warehouse.owner = $owner.
Here is the code :
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if ($this->security->isGranted('SCOPE admin') || !$this->security->isGranted('SCOPE logistician')) {
return;
}
/** #var Client $client */
$client = $this->security->getUser();
$rootAlias = $queryBuilder->getRootAliases()[0];
switch ($resourceClass) {
case Entry::class:
case Inventory::class:
$queryBuilder->join(sprintf('%s.stock', $rootAlias), 's');
break;
case Reference::class:
// I'm here
$queryBuilder->join(sprintf('%s.stocks', $rootAlias), 's');
break;
default:
return;
break;
}
$queryBuilder->join('s.warehouse', 'w', Expr\Join::WITH, 'w.owner = :owner');
$queryBuilder->setParameter('owner', $client->getSubject());
}
Here is the response :
{
"#id": "/stock/references/7a00e32f-1195-43e6-a5d1-f75d64471112",
"#type": "Reference",
"id": "7a00e32f-1195-43e6-a5d1-f75d64471112",
"quantityAvailable": 75,
"quantitiesOnHand": 100,
"quantityOfExpectedEntries": -25,
"archived": false,
"stocks": [
{
"#id": "/stock/stocks/214d9b27-d2c8-45e5-9d67-10985291022a",
"#type": "Stock",
"quantityOnHand": 50,
"lastCountedEntryWasCreatedAt": "2020-07-23T10:04:32+02:00",
"warehouse": {
"#id": "/stock/warehouses/3a61275f-4b20-4061-a64e-52783cf4d892",
"#type": "Warehouse",
"id": "3a61275f-4b20-4061-a64e-52783cf4d892",
"owner": "9001",
"name": "Adams-Reichel",
"createdAt": "2020-07-23T10:04:32+02:00"
},
"createdAt": "2020-07-23T10:04:32+02:00"
},
{
"#id": "/stock/stocks/6f2a0542-d65d-489a-b96c-c8658ff195ea",
"#type": "Stock",
"quantityOnHand": 50,
"lastCountedEntryWasCreatedAt": "2020-07-23T10:04:32+02:00",
"warehouse": {
"#id": "/stock/warehouses/cc8f3267-29b6-4ad5-9f8b-74b98aab85d6",
"#type": "Warehouse",
"id": "cc8f3267-29b6-4ad5-9f8b-74b98aab85d6",
"owner": "9002",
"name": "Steuber, Ruecker and Vandervort",
"createdAt": "2020-07-23T10:04:32+02:00"
},
"createdAt": "2020-07-23T10:04:32+02:00"
}
],
"createdAt": "2020-07-23T10:04:32+02:00"
},
How can I filter the subentity result by just using my extension and DQL ? Thanks
Extension is used for retrieve main items, not associated collection.
Instead you can use data transformer.
class ReferenceOutput {
public $stocks;
}
//Next class
use ApiPlatform\Core\DataTransformer\DataTransformerInterface;
use Symfony\Component\Security\Core\Security;
class ReferenceOutputTransformer implements DataTransformerInterface
{
private string $security;
public function __construct(Security $security)
{
$this->security = $security;
}
/**
* Transforms the given object to something else, usually another object.
* This must return the original object if no transformations have been done.
*
* #param object $object Snapshot
*
* #param string $to
* #param array $context
*
* #return object
*/
public function transform($object, string $to, array $context = [])
{
/** #var Reference $reference */
$reference = $object;
/** #var Client $client */
$client = $this->security->getUser();
$referenceOutput = new ReferenceOutput();
$stocks = $reference->getStoks()->filter(
static function($key, $stock) use ($client) {
return $stock->getWarehouse()->getOwner() === $client->getSubject();
});
$referenceOutput->stocks = $stocks;
return $referenceOutput;
}
/**
* Checks whether the transformation is supported for a given data and context.
*
* #param object|array $data object on normalize / array on denormalize
*
* #param string $to
* #param array $context
*
* #return bool
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
return ReferenceOutput::class === $to && $data instanceof Reference;
}
}
"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 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,
* }
* }
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"
}
}