Symfony Rest Controller Limiting serialization depth - php

I have entity bit and relation ManyToMany developer, for developer I use * #Groups({"for_project"}) and when I use this anotation, in response not see fields developer, I look documentation and see * #MaxDepth(2) and use this but still have null. Why ?
{
"0": {
"id": 501,
"created": "2015-11-27T12:25:11+0200",
"developer_id": {},
"rate": 4,
"comment": "fsefsf"
},
"1": {
"id": 502,
"created": "2015-11-27T12:25:46+0200",
"developer_id": {},
"rate": 3,
"comment": "feasf"
}
}
class Bit
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose()
* #Groups({"for_project"})
*/
private $id;
/**
* #var datetime $created
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
* #Expose()
* #Groups({"for_project"})
*/
private $created;
/**
* #var datetime $updated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime")
* #Groups({"for_project"})
*/
private $updated;
/**
* #var \Artel\ProfileBundle\Entity\Developer
*
* #ORM\ManyToOne(targetEntity="Developer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="developer_id", referencedColumnName="id", onDelete="CASCADE")
* })
* #Expose()
* #Groups({"for_project"})
* #MaxDepth(2)
*/
private $developerId;
class BitController extends FOSRestController
...anotaion block
public function getBitByProjectAction($id, $token)
{
$this->getDoctrine()->getRepository('ArtelProfileBundle:Users')->findOneBySecuritytoken($token);
if (!empty($user) || $security->isGranted('ROLE_ADMIN') ) {
$bits = $this->getDoctrine()->getManager()
->getRepository('ArtelProfileBundle:Bit')
->findBitByProject($id, $token);
if (!$bits) {
throw new NotFoundHttpException();
}
return View::create()
->setStatusCode(200)
->setData($bits)
->setSerializationContext(
SerializationContext::create()
->enableMaxDepthChecks()
->setGroups(array("for_project"))
);
SOLVED
I understand, need add * #Groups({"for_project"}) for fields entity developer
but when I deleted #MaxDepth I still have fields entity developer, why need #MaxDepth ?
I understand, when use without #MaxDept, we have maximum depth for relation fields, Example I have entity Bit, bit have Developer and developer have User, if I want visible fields entity User I need add #MaxDept(3) for field developer in entity Bit

when use without #MaxDept, we have maximum depth for relation fields, Example I have entity Bit, bit have Developer and developer have User, if I want visible fields entity User I need add #MaxDept(3) for field developer in entity Bit
in action:
return View::create()
->setStatusCode(200)
->setData($bits)
->setSerializationContext(
SerializationContext::create()
->enableMaxDepthChecks()
->setGroups(array("for_project"))
);
and in response
[
{
"id": 501,
"created": "2015-11-30T17:49:19+0200",
"developer_id": {
"id": 201,
"rate": 0,
"user": [
{
"first_name": "Ivan",
"last_name": "Shuba"
}
]
},
"rate": 4,
"comment": "fsefse"
}
]
and Entity
class Bit
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose()
* #Groups({"for_project"})
*/
private $id;
/**
* #var datetime $created
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
* #Expose()
* #Groups({"for_project"})
*/
private $created;
/**
* #var datetime $updated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime")
* #Groups({"for_project"})
*/
private $updated;
/**
* #var \Artel\ProfileBundle\Entity\Developer
*
* #ORM\ManyToOne(targetEntity="Developer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="developer_id", referencedColumnName="id", onDelete="CASCADE")
* })
* #Expose()
* #Groups({"for_project"})
* #MaxDepth(3)
*/
private $developerId;
class Developer
{
/**
* #ORM\OneToMany(targetEntity="Artel\ProfileBundle\Entity\Users", mappedBy="developer", cascade={"persist", "remove"})
* #Expose()
* #Groups({"for_project"})
*/
protected $user;
class Users implements UserInterface
{
/**
* #var string
*
* #ORM\Column(name="first_name", type="string", length=255, nullable=false)
* #Expose()
* #Assert\Length(min=3, max=255)
* #Groups({"for_project"})
*/
protected $firstName;
/**
* #var string
*
* #ORM\Column(name="last_name", type="string", length=255, nullable=true)
* #Expose()
* #Groups({"for_project"})
*/
protected $lastName;

This works for me using fos rest version: 2* and JMS serializer version: 3*
return $this->view($data)->setContext((new Context())->enableMaxDepth());

Related

Why my API-Plateform resource give me always and only 2 attributes in response?

I try to create a new API rest on monolithic project with symfony 4.4, api-platform 2.6.8, i have to use yaml configuration, but i have the same issue with annotation.
One of my resource send 2 attributes (ID and label) when i try GET request on collection, whatever the configuration.
The entity :
/**
* Structure.
*
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="por_structure")
* #ORM\Entity(repositoryClass="Common\StructureBundle\Repository\StructureRepository")
* #UniqueEntity(fields={"parent", "code", "status"}, errorPath="code", message="common.structure.code.unique")
* #UniqueEntity(fields={"parent", "label", "status"}, errorPath="label", message="common.structure.label.unique")
*/
class Structure
{
/**
* ID of the structure node.
*
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* Label of the structure node.
*
* #var string
* #Assert\NotBlank()
* #ORM\Column(name="label", type="string", length=255, nullable=false)
*/
private $label;
/**
* External ID of the structure node.
*
* #var string
* #Assert\NotBlank()
* #ORM\Column(name="code", type="string", length=255, nullable=false)
*/
private $code;
/**
* Accounting code of the structure node.
*
* #var string
* #ORM\Column(name="accounting_code", type="string", length=255, nullable=true)
*/
private $accountingCode;
/**
* Status of the node (1: enabled, 0:disabled).
*
* #var bool
* #ORM\Column(name="status", type="boolean")
*/
private $status;
/**
* #Gedmo\TreeLeft
* #ORM\Column(name="lft", type="integer")
*/
private $lft;
/**
* #Gedmo\TreeLevel
* #ORM\Column(name="lvl", type="integer")
*/
private $lvl;
/**
* #Gedmo\TreeRight
* #ORM\Column(name="rgt", type="integer")
*/
private $rgt;
/**
* #var Structure
* #Gedmo\TreeRoot
* #ORM\ManyToOne(targetEntity="Common\StructureBundle\Entity\Structure")
* #ORM\JoinColumn(name="root", referencedColumnName="id", onDelete="CASCADE")
*/
private $root;
[...]
The resources.yaml :
resources:
#Structure
Common\StructureBundle\Entity\Structure:
shortName: 'Common/Structure'
properties:
id:
identifier: true
collectionOperations:
get:
openapi_context:
summary: 'Récupère une collection de Structure'
description: 'Récupère une collection de Structure'
normalization_context:
groups: [ 'a2:read:Structure']
openapi_definition_name: 'collection-read'
And the serializer.yaml (result is the same without any attribute in it):
Common\StructureBundle\Entity\Structure:
attributes:
id:
groups: [ 'a2:read:Structure' ]
label:
groups: [ 'a2:read:Structure' ]
code:
groups: [ 'a2:read:Structure' ]
status:
groups: [ 'a2:read:Structure' ]
The GET answer is still :
{
"#context": "\/api\/contexts\/Common\/Structure",
"#id": "\/api\/v2\/common\/structures",
"#type": "hydra:Collection",
"hydra:member": [
{
"id": 1,
"label": "SOCIETE 1 (FR)"
},
{
"id": 2,
"label": "Défaut"
},
{
"id": 3,
"label": "ZForfait jours"
},
[...]
],
"hydra:totalItems": 42
}
What did i do wrong ?
OK i found the problem.
I work on a big application and somewhere i didn't know, there is a serialization process which used classes implementing NormalizerInterface.
in these classes there is the definition which locked the output.
I change NormalizerInterface, ContextAwareNormalizerInterface, and add something special in the context when thes are called by the other part of the app.

Api-Platform: Eager Loading a Subresource

Hello guys I just started using Api-Platform and I got stuck on this problem for several hours.
I have a Symfony4 Project and two Entities: Bill & Abo
Bill:
/**
* #ORM\Entity(repositoryClass="App\Repository\BillRepository")
* #ApiResource
*/
class Bill {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="date", nullable=false)
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Abo", inversedBy="bills")
* #ORM\JoinColumn
* #ApiSubresource
*/
private $abo;
}
Abo:
/**
* #ORM\Entity(repositoryClass="App\Repository\AboRepository")
* #ApiResource
*/
class Abo
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private $name;
/**
* #var integer
*
* #ORM\Column(name="price", type="integer", nullable=false)
*/
private $price;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Bill", mappedBy="abo")
*/
private $bills;
}
When I now call this url, /api/bills, I get get back this data:
{
"#id": "/api/bills/14",
"#type": "Bill",
"id": 14,
"date": "2018-03-08T00:00:00+00:00",
"abo": "/api/abos/1"
},
...
But instead of this "abo": "/api/abos/1" I want the Abo data already loaded, something like this:
"abo": {
"name": "TestAbo",
"price": 25
}
Is this possible and if yes how can I achieve this?
Thanks for your time and help!
You can use serialization groups for this. Make sure that your relation field $abo and it's member fields groups are exposed.
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity(repositoryClass="App\Repository\BillRepository")
* #ApiResource(attributes={
* "normalization_context"={"groups"={"bill", "bill-abo", "abo"}}
* })
*/
class Bill {
...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Abo", inversedBy="bills")
* #ORM\JoinColumn
* #ApiSubresource
* #Groups("bill-abo")
*/
private $abo;
...
}
class Abo {
...
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=50, nullable=false)
* #Groups("abo")
*/
private $name;
...
}
You can read more in the docs.

Symfony JMSSerializer create object entity and relation entities and set

I use api full contact service and when I have json data from service I need set in DB. I have entity ApiFullContact and relation OneToMany entity socialProfiles, because in response have many social profiles and need set data like with JMS serializer set only ApiFullContact and set relation entity, I try use Accessor then VirtualProperty but still not set socialProfiles entities, this my entities:
* #ExclusionPolicy("all")
*/
class ApiFullContact
{
use Timestampable;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #Expose()
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="status", type="integer", nullable=true)
* #Expose()
*/
protected $status;
/**
* #var string
*
* #ORM\Column(name="likelihood", type="integer", nullable=true)
* #Expose()
*/
protected $likelihood;
/**
* #var string
*
* #ORM\Column(name="requestId", type="string", nullable=true)
* #Expose()
* #SerializedName("requestId")
*/
protected $requestId;
/**
* #var \Artel\ProfileBundle\Entity\CodeProfileLinks
*
* #ORM\OneToMany(targetEntity="Artel\ProfileBundle\Entity\CodeProfileLinks", mappedBy="apiFullContact", cascade={"persist", "remove"})
* #Expose()
* #SerializedName("socialProfiles")
* #Accessor()
* #Type("Artel\ProfileBundle\Entity\CodeProfileLinks")
*/
private $socialProfiles;
and relation entity
class CodeProfileLinks
{
use Timestampable;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="url", type="string", nullable=true)
* #Expose()
*/
private $url;
/**
* #var string
*
* #ORM\Column(name="type", type="string", nullable=true)
* #Expose()
*/
private $type;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=true)
* #Expose()
*/
protected $username;
in my action
$apiFullContact = $this->get('serializer')->deserialize(
$data,
'Artel\ProfileBundle\Entity\ApiFullContact',
'json'
);
and need set
and response from service:
{
"status": 200,
"likelihood": 0,
"requestId": "4076a34b-f14e-4331-a999-641be7feb2d5",
"contactInfo": {
"familyName": "Name",
"fullName": "Your Name",
"givenName": "Your"
},
"organizations": [],
"demographics": [],
"socialProfiles" : {
"twitter" : [ {
"followers" : 1,
"following" : 1,
"type" : "twitter",
"typeId" : "twitter",
"typeName" : "Twitter",
"url" : "https://twitter.com/Shuba_Vanya",
"username" : "Shuba_Vanya",
"id" : "2294488585"
} ],
"github" : [ {
"type" : "github",
"typeId" : "github",
"typeName" : "Github",
"url" : "https://github.com/shubaivan",
"username" : "shubaivan"
} ]
},
}
and how set entity ApiFullContact with relation entity socialProfiles data, how to set relation entities ???

JMS Deserialize ArrayCollection in Object

I'm trying to use JMS serializer in my application (not Symfony) and would like to deserialize a JSON object to the Doctrine Entity.
The plain properties are getting properly deserialized, but I can't get the ArrayCollections to work.
This is an excerpt of my product JSON:
{
"id": 2,
"name": "Shirt blue",
"attributeValues": [
{
"id": 4,
"title": "S",
"attributeId": 2
},
{
"id": 7,
"title": "Eterna",
"attributeId": 3
}
]
}
This is my Product entity:
<?php
namespace Vendor\App\Common\Entities;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="product")
* #JMS\ExclusionPolicy("all")
*/
class Product extends AbstractEntity {
/**
* #var int $id
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(name="id", type="integer", nullable=false)
* #JMS\Groups({"search"})
* #JMS\Expose
* #JMS\Type("integer")
*/
protected $id;
/**
* #var string $name
* #ORM\Column(name="name", type="string", nullable=false)
* #JMS\Expose
* #JMS\Groups({"search"})
* #JMS\Type("string")
*/
protected $name;
/**
* #var ArrayCollection $attributeValues
* #ORM\ManyToMany(targetEntity="Vendor\App\Common\Entities\Attribute\Value")
* #ORM\JoinTable(name="products_values",
* joinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="value_id", referencedColumnName="id")}
* )
* #JMS\Expose
* #JMS\MaxDepth(2)
* #JMS\Groups({"search"})
* #JMS\Type("ArrayCollection<Vendor\App\Common\Entities\Attribute\Value>")
*/
protected $attributeValues;
public function __construct() {
$this->attributeValues = new ArrayCollection();
}
/**
* #return ArrayCollection
*/
public function getAttributeValues() {
return $this->attributeValues;
}
/**
* #param ArrayCollection $attributeValues
*/
public function setAttributeValues($attributeValues) {
$this->attributeValues = $attributeValues;
}
/**
* #param Value $attributeValue
*/
public function addAttributeValue($attributeValue) {
$this->attributeValues->add($attributeValue);
}
/**
* #param Value $attributeValue
*/
public function removeAttributeValue($attributeValue) {
$this->attributeValues->removeElement($attributeValue);
}
}
This is my Value entity that should be deserialized in the ArrayCollection:
<?php
namespace Vendor\App\Common\Entities\Attribute;
use Vendor\App\Common\Entities\AbstractEntity,
Doctrine\ORM\Mapping as ORM,
JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
* #ORM\Table(name="attribute_value")
* #JMS\ExclusionPolicy("all")
*/
class Value extends AbstractEntity {
/**
* #var int $id
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(name="id", type="integer", nullable=false)
* #JMS\Expose
* #JMS\Groups({"search"})
* #JMS\Type("integer")
*/
protected $id;
/**
* #var string $title
* #ORM\Column(name="title", type="string", nullable=false)
* #JMS\Expose
* #JMS\Groups({"search"})
* #JMS\Type("string")
*/
protected $title;
/**
* #var int $attributeId
* #ORM\Column(name="attribute_id", type="integer", nullable=false)
* #JMS\Expose
* #JMS\Groups({"search"})
* #JMS\Type("integer")
*/
protected $attributeId;
/**
* OWNING SIDE
* #var \Vendor\App\Common\Entities\Attribute $attribute
* #ORM\ManyToOne(targetEntity="Vendor\App\Common\Entities\Attribute", inversedBy="values")
* #ORM\JoinColumn(name="attribute_id", referencedColumnName="id")
* #JMS\Expose
* #JMS\Groups({"search"})
* #JMS\Type("Vendor\App\Common\Entities\Attribute")
*/
protected $attribute;
//Getters and setters ...
}
Just trying to simply deserialize the entity:
$serializer = SerializerBuilder::create()->build();
$entity = $serializer->deserialize($sourceJson, Product::class, 'json');
But the attributeValue ArrayCollection stays empty. What am I missing?
I found the solution. JMS has a default naming strategy which converts camelcase to underscore notation.
Default is that the naming strategy either looks for the annotation #SerializedName and if that is not set, it will convert CamelCase to underscore.
So the property just got ignored because it didn't match the expected name. Of course, it would be better if there was an error or a notification which gives a hint here on where to search for the problem (something like unknown property would have been nice).
$serializer = SerializerBuilder::create()->setPropertyNamingStrategy(new IdenticalPropertyNamingStrategy())->build();
Was the solution.

Symfony2 - Serialize object with relations (ManyToMany, OneToMany ...)

Can someone tell me if it's possible to serialize an entity that have relations ?
json_encode already works but my object looks like this :
{
"id": 1,
"lot": 32,
"num": "533987",
"date_modification": {
"date": "2015-02-17 14:24:52",
"timezone_type": 3,
"timezone": "Europe/Paris"
},
"customer": {
"id": 1,
"lastname": "DUFRESNE",
"firstname": "CHRISTOPHE",
}
}
But i would like to serialize data withotu sub objects. in fact like this :
{
"id": 1,
"lot": 32,
"num": "533987",
"date_modification": "2015-02-17",
"customer": "DUFRESNE CHRISTOPHE",
}
}
So i checked the doc : http://symfony.com/doc/current/components/serializer.html
But i can't see if it's possible to works with relations like ManyToMany, etc and how to do it ?
Here is my Entity :
/**
* Subscription (BS)
*
* #ORM\Table(name="subscription")
* #ORM\Entity(repositoryClass="Jcd\LiteyearBundle\Entity\SubscriptionRepository")
* #ORM\HasLifecycleCallbacks
*/
class Subscription {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="lot", type="smallint")
*/
private $lot;
/**
* #var string
* #ORM\Column(name="num", type="string", length=9)
*/
private $num;
/**
* #var \DateTime
* #ORM\Column(name="date_creation", type="datetime", nullable=true)
*/
private $date_creation;
/**
* #var \DateTime
* #ORM\Column(name="date_modification", type="datetime", nullable=true)
*/
private $date_modification;
/**
* #var \DateTime
* #ORM\Column(name="date_completeness", type="datetime", nullable=true)
*/
private $date_completeness;
/**
* #var \DateTime
* #ORM\Column(name="date_reception", type="datetime", nullable=true)
*/
private $date_reception;
/**
* #var \DateTime
* #ORM\Column(name="date_signature", type="datetime", nullable=true)
*/
private $date_signature;
/**
* #var \DateTime
* #ORM\Column(name="date_entry", type="datetime", nullable=true)
*/
private $date_entry;
/**
* #var \Integer
* #ORM\Column(name="statement", type="integer", nullable=true)
*/
private $statement; // Releve du compteur
/**
* #var \Integer
* #ORM\Column(name="statement_kind", type="integer", nullable=true)
*/
private $statement_kind; // Personne qui a relevé le compteur
/**
* #var \Integer
* #ORM\Column(name="car", type="integer", nullable=true)
*/
private $car; // Consommation annuelle de reference
/**
* #var boolean
* #ORM\Column(name="pro", type="boolean", options={"default" = false}, nullable=true)
*/
private $pro;
/**
* #ORM\ManyToOne(targetEntity="Jcd\LiteyearBundle\Entity\User", cascade={"persist"}, inversedBy="adv_subscriptions")
* #ORM\JoinColumn(name="adv_id", referencedColumnName="id", nullable=true)
*/
private $adv;
/**
* #ORM\ManyToOne(targetEntity="Jcd\LiteyearBundle\Entity\User", cascade={"persist"}, inversedBy="vendor_subscriptions")
* #ORM\JoinColumn(name="vendor_id", referencedColumnName="id", nullable=true)
*/
private $vendor;
/**
* #var \Integer
* #ORM\Column(name="provider", type="integer")
*/
private $provider;
/**
* #var \Integer
* #ORM\Column(name="state", type="integer")
*/
private $state;
/**
* #var \Integer
* #ORM\Column(name="payment", type="integer")
*/
private $payment;
/**
* #var \Integer
* #ORM\Column(name="billing", type="integer")
*/
private $billing;
/**
* #var string
* #ORM\Column(name="comment", type="text", nullable=true)
*/
private $comment;
/**
* #var float
* #ORM\Column(name="wage", type="float", nullable=true)
*/
private $wage; // Anciennement Rem
/**
* #var float
* #ORM\Column(name="com", type="float", nullable=true)
*/
private $com; // Anciennement Commission
/**
* #var string
* #ORM\Column(name="offer", type="text", nullable=true)
*/
private $offer; // Anciennement Prix fixe
/**
* #var \Integer
* #ORM\Column(name="mail_kind", type="integer", nullable=true)
*/
private $mail_kind; //Anciennement Mail
/**
* #ORM\OneToMany(targetEntity="SubscriptionTrack", cascade={"persist", "remove"}, mappedBy="subscription")
*/
private $tracks;
/**
* #ORM\ManyToMany(targetEntity="SubscriptionSalary", cascade={"persist"}, mappedBy="subscriptions")
**/
private $salaries;
/**
* #ORM\OneToOne(targetEntity="Jcd\LiteyearBundle\Entity\Customer")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
**/
private $customer;
Thank you for you answer !
The easiest way to achieve what you want is to use JMSSerializerBundle that has many features that you might want to use.
Give it a try: http://jmsyst.com/bundles/JMSSerializerBundle

Categories