Allowing NULL value in json with API-Platform - php

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

Related

NelmioApiDocBundle annotations for nested objects

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": [{}]
}]
}

Doctrine Extension with API Platform - SQL

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

Create a custom route for a collection using GET method

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

Only return specific fields for specific groups when serializing with Symfony 4

Symfony 4. I have two entities, Cat and Owner.
class Cat
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups("cats")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("cats")
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Owner", mappedBy="cat")
* #Groups("cats")
*/
private $owners;
}
class Owner
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups("cats")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("owners")
*/
private $name;
}
My API endpoint needs to return 2 keys, owners (a list of all owners), and cats (a list of all cats, with their owners).
public function index()
{
$repository = $this->getDoctrine()->getRepository(Owner::class);
$owners = $repository->findAll();
$repository = $this->getDoctrine()->getRepository(Cat::class);
$cats = $repository->findAll();
return $this->json([
'owners' => $owners,
'cats' => $cats,
], 200, [], ['groups' => ['owners', 'cats']]);
}
This works, but with 1 problem: the cats list contains the full owner information for each owner, i.e.:
{
"owners": [
{
"id": 1,
"name": "John Smith"
}
],
"cats": [
{
"id": 1,
"name": "Miaow",
"owners": [
{
"id": 1,
"name": "John Smith"
}
]
}
]
}
What I want is for the owners key in the cat object to only return the owner's id, like this:
{
"owners": [
{
"id": 1,
"name": "John Smith"
}
],
"cats": [
{
"id": 1,
"name": "Miaow",
"owners": [
1
]
}
]
}
You can use a getter for a specific group and with a specific serialized name.
In Cat:
/**
* #Groups("cats")
* #SerializedName("owners")
*/
public function getOwnersIds(): iterable
{
return $this->getOwners()->map(function ($owner) {
return $owner->getId();
})->getValues();
}
Override the mapping of a field.
See doctrine documentation:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html

Get children resources URIs with the Hateoas library for PHP

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?

Categories