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": [{}]
}]
}
Related
My question is rather simple, but I didn't find any clues on the Internet after googling for one hour.
I'm trying to build an Symfony API, but when returning json output, it lazy loads, every relation into the output. While this is not such a big deal (in most cases), its really bad when it does this trick with user information. So everything (password, email, etc.) is displayed.
My question is: Is it possible to mark an entity in doctrine, as protected, so the autoload will not be made, with this entity? In some cases it comes pretty handy but this is a big flaw. If its not possible to mark an entity, is it possible to deactivate it completely, or on an Collection Element?
Thanks in advance
EDIT:
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Profile", mappedBy="user", cascade={"persist", "remove"})
*/
private $profile;
getters and setters are there.
And there is a Profile class, that is the interface, for all relations. It has an 1to1 relation.
class Profile
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\User", inversedBy="profile", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $user;
getters and setters are there to.
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Profile", inversedBy="ownedEvents")
* #ORM\JoinColumn(nullable=false)
*/
private $profile;
/**
* #ORM\OneToMany(targetEntity=Post::class, mappedBy="event", orphanRemoval=true)
*/
private $posts;
The problem ist, that this profile is loaded, and with it the user...
The following is the controller function. But the serialization is happening in an extra method.
public function getUnreactedEvents(): JsonResponse{
$events = $this->getDoctrine()
->getManager()
->getRepository(Event::class)
->getUnreactedEvents($this->profileUtils->getLoggedInProfileFromDatabase()->getId());
return new JsonResponse($this->eventUtils->eventsToArray($events));
}
here is the to array function. (There is a base class so there are two mathods:
\Utils class:
\\Utils class:
public function eventsToArray($events): array{
return $this->toArray($events, array("usrEvntSts"));
}
\\Base class:
protected function toArray($objects, $fieldsToBeRemoved): array{
$normalizers = [new DateTimeNormalizer(), new ObjectNormalizer()];
$serializer = new Serializer($normalizers);
if(!is_array($objects)){
$objects = array($objects);
}
//normalizes the objects object, for circular references, returns id of the object
//doctrine comes with own array format
$objectsArray = $serializer->normalize($objects, 'array', [
'circular_reference_handler' => function ($object) {
return $object->getId();
}
]);
//some keys have to be erased from the event response
foreach ($objectsArray as $key => $object) {
if (method_exists($objects[0], "getProfile")){
/** #var Profile $profile */
$profile = $objects[$key]->getProfile();
unset($objectsArray[$key]["profile"]);
$objectsArray[$key]["profile"]['id'] = $profile->getId();
}
foreach ($fieldsToBeRemoved as $field){
unset($objectsArray[$key][$field]);
}
}
return $objectsArray;
}
}
As you see, my first idea was to just delete the field. But afer I added an new entity relation (posts), which has an owner profile too. The user class is loaded again...
Output:
{
"id": 1,
"name": "xcvxycv",
"date": "2020-06-28T18:08:55+02:00",
"public": false,
"posts": [
{
"id": 1,
"date": "2020-06-30T00:00:00+02:00",
"content": "sfdnsdfnslkfdnlskd",
"profile": {
"id": 2,
"user": {
"id": 2,
"email": "alla",
"username": "alla",
"roles": [
"ROLE_USER"
],
"password": "$argon2id$v=19$m=65536,t=4,p=1$a01US1dadGFLY05Lb1RkcQ$npmy0HMf19Neo/BnMqXGwkq8AZKVSCAEmDz8mVHLaQ0",
"salt": null,
"apiToken": null,
"profile": 2
},
"username": "sdfsdf",
"usrEvntSts": [],
"ownedEvents": [
{
"id": 3,
"name": "blaaaa",
"date": "2020-06-28T18:08:55+02:00",
"profile": 2,
"public": false,
"usrEvntSts": [],
"posts": [
{
"id": 2,
"date": "2020-06-30T00:00:00+02:00",
"content": "sfdnsdfnslkfdnlskd",
"profile": 2,
"event": 3,
"comments": []
}
]
},
And it goes on and on and on....
I would suggest to use JMSSerializerBundle for that. It is a widely used bundle, also in huge API's. You can exactly configure which properties should be exposed and which not. You can also build groups for exposing properties and use a specific exclusion strategy. Check the documentation for further information.
Hint: also check the Limiting serialization depth for deep nested objects.
"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"
}
}
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
My consumed XML API has an option to retrieve only parts of the response.
This causes the resulting object to have a lot of NULL properties if this feature is used.
Is there a way to actually skip NULL properties? I tried to implement an exclusion strategy with
shouldSkipProperty(PropertyMetadata $property, Context $context)`
but i realized there is no way to access the current property value.
An example would be the following class
class Hotel {
/**
* #Type("string")
*/
public $id;
/**
* #Type("integer")
*/
public $bookable;
/**
* #Type("string")
*/
public $name;
/**
* #Type("integer")
*/
public $type;
/**
* #Type("double")
*/
public $stars;
/**
* #Type("MssPhp\Schema\Response\Address")
*/
public $address;
/**
* #Type("integer")
*/
public $themes;
/**
* #Type("integer")
*/
public $features;
/**
* #Type("MssPhp\Schema\Response\Location")
*/
public $location;
/**
* #Type("MssPhp\Schema\Response\Pos")
*/
public $pos;
/**
* #Type("integer")
*/
public $price_engine;
/**
* #Type("string")
*/
public $language;
/**
* #Type("integer")
*/
public $price_from;
}
which deserializes in this specific api call to the following object with a lot of null properties.
"hotel": [
{
"id": "11230",
"bookable": 1,
"name": "Hotel Test",
"type": 1,
"stars": 3,
"address": null,
"themes": null,
"features": null,
"location": null,
"pos": null,
"price_engine": 0,
"language": "de",
"price_from": 56
}
]
But i want it to be
"hotel": [
{
"id": "11230",
"bookable": 1,
"name": "Hotel Test",
"type": 1,
"stars": 3,
"price_engine": 0,
"language": "de",
"price_from": 56
}
]
You can configure JMS Serializer to skip null properties like so:
$serializer = JMS\SerializerBuilder::create();
$serializedString = $serializer->serialize(
$data,
'xml',
JMS\SerializationContext::create()->setSerializeNull(true)
);
Taken from this issue.
UPDATE:
Unfortunately, if you don't want the empty properties when deserializing, there is no other way then removing them yourself.
However, I'm not sure what your use case for actually wanting to remove these properties is, but it doesn't look like the Hotel class contains much logic. In this case, I'm wondering whether the result has should be a class at all ?
I think it would be more natural to have the data represented as an associative array instead of an object. Of course, JMS Serializer cannot deserialize your data into an array, so you will need a data transfer object.
It's enough that you add dumpArray and loadArray methods to your existing Hotel class. These will be used for transforming the data into your desired result and vice versa. There is your DTO.
/**
* Sets the object's properties based on the passed array
*/
public function loadArray(array $data)
{
}
/**
* Returns an associative array based on the objects properties
*/
public function dumpArray()
{
// filter out the properties that are empty here
}
I believe it's the cleanest approach and it might reflect what you're trying to do more.
I hope this helps.