Use existing database entry for foreign key in Doctrine - php

Given already persisted objects for categories. How do one reuse the existing categories in a one-to-many relationship in new objects when only the id of the cateories is known
/** #Entity() */
class Category {
/**
* #var string
* #\Doctrine\ORM\Mapping\Column(type="string")
*/
private $name;
/** #var string
* #\Doctrine\ORM\Mapping\Id()
* #\Doctrine\ORM\Mapping\Column(type="string")
*/
private $id;
/**
* Category constructor.
* #param string $name
* #param string $id
*/
public function __construct($name, $id)
{
$this->name = $name;
$this->id = $id;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return string
*/
public function getId()
{
return $this->id;
}
}
And now I have lets say two categories id=1 -> fiction and id=2 -> english book.
Now I know the ids of the categories and want to save the one-to-many relations in my Book object.
/** #Entity() */
class Book {
/** #var mixed
* #\Doctrine\ORM\Mapping\OneToMany()
*/
private $categories;
/** #var string */
private $title;
/**
* Book constructor.
* #param mixed $categories
* #param string $title
*/
public function __construct($categories, $title)
{
$this->categories = $categories;
$this->title = $title;
}
/**
* #return mixed
*/
public function getCategories()
{
return $this->categories;
}
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
Is it possible to create and persist a book object by hand with references an existing and already persisted object from which I do only know the id?

You can't achieve this without retrieving at least a Reference proxy :
$categoriesRefs = ['english' => 1, 'fiction' => 2];
$categories = [];
foreach($categoriesRefs as $ref){
$categories[] = $em->getReference('Namespace\Entity\Category', $ref));
}
$book = new Book($categories, 'title');
$em->persist($book);
$em->flush();
You can store categories without fetching the whole Category objects.
Read more about reference proxy.

Related

Doctrine2 ManyToMany bidirectionnal attribute populated by find method

I want to create my entities from an existing database, it's an N - N relation which create articlesCategories table.
I currently have 2 entities, Article and Category, and I want to have a ManyToMany bidirectionnal relationship.
But when I try to get article or category with a findByXXX or findOneByXXX method, my ManyToMany attribute is NULL or I have an ORM Exception : Entity 'App\Models\Article' has no field 'categories'. You can therefore not call 'findByCategories' on the entities' repository
Database :
Table article : idArticle, name, description, priceHT, size
Table category : idCategory, name, percentTaxe
Table articlesCategories : idArticle, idCategory
Entities :
Category
/**
* #Entity
* #Table(name="category")
*/
class Category
{
/**
* #var integer
*
* #Id
* #Column(name="idCategory", type="integer", nullable=false)
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Column(name="name", type="string", length=45, nullable=false)
*/
private $name;
/**
* #var string
* #Column(name="percentTaxe",type="decimal", precision=10, scale=0, nullable=false)
*/
private $percentTaxe;
/*
* Many categories have many articles
* #ManyToMany(targetEntity="App\Models\Article", inversedBy="categories")
* #JoinTable(name="articlesCategories",
* joinColumns={#JoinColumn(name="idCategory", referencedColumnName="idCategory")},
* inverseJoinColumns={#JoinColumn(name="idArticle", referencedColumnName="idArticle")}
* )
*/
private $articles;
/*Constructor*/
public function __construct(){
$toto = "toto";
var_dump($toto);
$this->articles = new ArrayCollection();
}
/***************************
Getters / Setters
****************************/
public function getId(){
return $this->id;
}
public function getName(){
return $this->name;
}
public function getPercentTaxe(){
return $this->percentTaxe;
}
public function getArticles(){
return $this->articles;
}
/************************/
public function setId($id){
$this->id = $id;
}
public function setName($name){
$this->name = htmlspecialchars($name);
}
public function setPercentTaxe($percentTaxe){
$this->percentTaxe = htmlspecialchars($percentTaxe);
}
public function setArticles(\Doctrine\Common\Collections\Collection $articles)
{
$this->articles = $articles;
}
/***************************
Getters / Setters
****************************/
public function addArticle(App\Models\Article $article)
{
var_dump($article);
$article->addCategory($this); // synchronously updating inverse side
$this->articles[] = $article;
}
Article
/**
* #Entity
* #Table(name="article")
*/
class Article
{
/**
* #var integer
*
* #Id
* #Column(name="idArticle", type="integer", nullable=false)
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Column(name="name", type="string", length=45, nullable=false)
*/
private $name;
/**
* #var string
* #Column(name="description",type="string", nullable=true)
*/
private $description;
/**
* #var string
* #Column(name="priceHT",type="decimal", precision=10, scale=3, nullable=false)
*/
private $priceHT;
/**
* #var string
* #Column(name="size", type="string", length=3, nullable=true)
*/
private $size;
/*
* Many articles have many categories
* #ManyToMany(targetEntity="App\Models\Category", inversedBy="articles")
* #JoinTable(name="articlesCategories",
* joinColumns={#JoinColumn(name="idArticle", referencedColumnName="idArticle")},
* inverseJoinColumns={#JoinColumn(name="idCategory", referencedColumnName="idCategory")}
* )
*/
private $categories;
/*Constructor*/
public function __construct(){
echo"tata";
$this->categories = new ArrayCollection();
echo"tata";
}
/***************************
Getters / Setters
****************************/
public function getId(){
return $this->id;
}
public function getName(){
return $this->name;
}
public function getDescription(){
return $this->description;
}
public function getPriceHT(){
return $this->priceHT;
}
public function getSize(){
return $this->size;
}
public function getCategories(){
return $this->categories;
}
/************************/
public function setId($id){
$this->id = $id;
}
public function setName($name){
$this->name = htmlspecialchars($name);
}
public function setDescription($description){
$this->description = htmlspecialchars($description);
}
public function setPriceHT($priceHT){
$this->priceHT = htmlspecialchars($priceHT);
}
public function setSize($size){
$this->size = htmlspecialchars($size);
}
public function setCategories($categories){
$this->categories = $categories;
}
/***************************
Getters / Setters
****************************/
public function addCategory(App\Models\Category $category)
{
$category->addArticle($this); // synchronously updating inverse side
$this->categories[] = $category;
}
/************************/
public function hydrate($data)
{
foreach($data as $key => $value)
{
// Get back the setter name wich correspond to the attribute
$method = 'set'.ucfirst($key);
// if the good setter exist.
if(methodexists($this, $method))
{
$this->$method($value);
}
}
}
}
Manager
/**
* #param category : category of article we want
* #return an array of Article object or null
*/
public function getArticlesByCategory($categoryName)
{
$articles = NULL;
$repository = $this->getEntityManager()->getRepository("App\Models\Category");
$category = $repository->findOneByName($categoryName);
var_dump($category);
if($category != NULL)
{
$articles = $category->getArticles();
}
return $articles;
}
And when I var_dump my $category, I have : class App\Models\Category#122 (4) { private $id => int(2) private $name => string(7) "clothes" private $percentTaxe => string(2) "20" private $articles => NULL }
I found my categories and articles are null instead of to be an empty array because of Doctrine create instances of mapped entities without invoking constructor but I don't understand why It doesn't populate it.
I just use Doctrine2, I don't use symfony.
First, you have an error at your annotations on categories and articles fields of both entities. To be valid they should start with /** not /*.Change:
/*
* Many categories have many articles
to
/**
* Many categories have many articles
Same for $categories field of Article Entity.
Also change one of the fields(articles or categories) to be the inverse side of the relation, e.g.
inversedBy="categories"
to
mappedBy="categories"
On the other part of your question, default EntityRepository find methods(findBy, findOneBy) of the does not support filter by many to many relation(at least not yet). You will have to do the extra effort and use createQueryBuilder, e.g.:
$qb = $doctrine->getRepository(Article::class)->createQueryBuilder('a');
$qb->select(
'a',
'cat'
)
->innerJoin( 'a.categories', 'cat' )
->where('cat.id =1');
And better create your own ArticleRepository and CategoryRepository and define your find methods there. 7.8.8. Custom repositories

Doctrine + Zend Framework 2: Insert array of data in a many to many relationship

I have an application with Zend Framework2 and Doctrine2 as ORM.
I have this Entity called User:
namespace Adm\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\Column(type="string")
*/
protected $email;
/**
* #ORM\Column(type="string")
*/
protected $password;
/**
* #ORM\ManyToMany(targetEntity="Module")
* #ORM\JoinTable(
* name="user_module",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="module_id", referencedColumnName="id")}
* )
*/
protected $modules;
public function __construct() {
$this->modules = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return the $id
*/
public function getId() {
return $this->id;
}
/**
* #return the $name
*/
public function getName() {
return $this->name;
}
/**
* #return the $email
*/
public function getEmail() {
return $this->email;
}
/**
* #return the $password
*/
public function getPassword() {
return $this->password;
}
/**
* #param field_type $id
*/
public function setId($id) {
$this->id = $id;
}
/**
* #param field_type $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* #param field_type $email
*/
public function setEmail($email) {
$this->email = $email;
}
/**
* #param field_type $password
*/
public function setPassword($password) {
$this->password = $password;
}
/**
* Add module
*
* #param dm\Entity\Module
* #return User
*/
public function addModules(Module $modules = null){
$this->modules[] = $modules;
}
/**
* Get modules
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getModules(){
return $this->modules;
}
}
See the modules property is a relation Many to Many with a table called user_modules.
And i have the Entity Module as well:
namespace Adm\Entity;
use Doctrine\ORM\Mapping as ORM;
class Module{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="integer")
*/
private $status;
/**
* #return the $id
*/
public function getId() {
return $this->id;
}
/**
* #return the $name
*/
public function getName() {
return $this->name;
}
/**
* #return the $status
*/
public function getStatus() {
return $this->status;
}
/**
* #param field_type $id
*/
public function setId($id) {
$this->id = $id;
}
/**
* #param field_type $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* #param field_type $status
*/
public function setStatus($status) {
$this->status = $status;
}
}
I receive a array variable containing the Post from a form to insert in a table. Each post element have it's property in Entity, as expected. Together, i have a $module variable which is an array containing id's of the modules. My question is: How do i insert this data in the user_module table?
My add function is this:
public function addUser($newUser){
$user = new User();
$user->setName($newUser['name']);
...
$this->getEm()->persist($user);
$this->getEm()->flush();
}
Firstly you need to have cascade={"persist"} as mentioned by #skrilled.
Then you need to retrieve the module entities from the database. You mentioned you have the id's in the $module variable.
You need a DQL statement something like this
$builder = $this->getEntityManager()->createQueryBuilder();
$builder->select('m')
->from('Adm\Entity\Module', 'm')
->where('m.id IN (:modules)')
->setParameter('modules', $modules);
$moduleEntities= $builder->getQuery()->getResult(Query::HYDRATE_OBJECT);
and in your User entity you will need
public function addModules(Array $moduleEntities)
{
foreach ($moduleEntities as $module) {
if ($module instanceof Module) {
$this->modules[] = $module;
}
}
return $this;
}
finally in your addUser method you will need to add the array of modules from the above DQL
public function addUser($newUser, $moduleEntities)
{
$user = new User();
$user->setName($newUser['name']);
....
$user->addModules($moduleEntities);
$this->getEm()->persist($user);
$this->getEm()->flush();
}
I hope this helps
You should read about using cascade. This will allow you to save/modify/remove the associated relationships and how you expect this to work.
In this case, you would want the relationship to persist since you want the associated entities to be saved when user itself is saved.
#ORM\ManyToMany(targetEntity="Module", cascade={"persist"})
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
By default, no operations are cascaded.
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
refresh : Cascades refresh operations to the associated entities.
all : Cascades persist, remove, merge, refresh and detach operations to associated entities.

Is there a way to insert many entities that reference a new entity all at once in Doctrine using ZF2 form?

I have a Product entity and a ProductImage entity. Product images are "ManyToOne" and contain a reference field to the product.
I'm using the Zend Framework 2 Form module with a "Collection" fieldset for the images. The form will populate with manually added rows in the database but does not create new rows or delete them.
The product image collection fieldset contains an id, url, and sort field for the image.
When i submit the form with new data, the DoctrineObject hydrator tries to load a ProductImage entity based on identifiers but ProductImage.id is NULL since there is no entity to reference obviously.
I tried creating my own hydrator to extend DoctrineObject and create the entities if they don't exist but the problem there is that Product does not have an ID to reference yet until it's created.
My question is do i need to go full custom on the images? Or is there a way to accomplish this using the DoctrineObject hydrator?
I am not shure if this will work for you, but I think I have a similar concept with a domain with OneToMany to a Properties table (one domain will have many properties)
Notice my code in Domain->addProperty() where I set the property's Domain id (pretty shure this will also work if the domain doesn't have an ID...
Hope this helps :)
public function indexAction()
{
$em = $this->getEntityManager();
$title = new Property();
/** #var \Common\Entity\Domain $domain */
$rep = $em->getRepository('Common\Entity\Domain');
$domain = $rep->findOneBy(array('id' => '3'));
$title->setProperty("title");
$title->setValue("WWW . RichardHagen . NO _ TEST");
$domain->addProperty($title);
$em->persist($domain);
$em->flush();
return new ViewModel(["domain" => $domain]);
}
This is my code for Domain:
<?php
/**
* Created by PhpStorm.
* User: Richard
* Date: 30.11.2014
* Time: 13:24
*/
namespace Common\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Common\Entity\Property;
/**
* #ORM\Entity
* #ORM\Table(name="domain")
*/
class Domain {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned":true})
*/
protected $id;
/** #ORM\Column(length=127,nullable=true) */
protected $subdomain;
/** #ORM\Column(length=255,nullable=true) */
protected $domain;
/**
* #ORM\OneToMany(targetEntity="Property", mappedBy="domain", fetch="EAGER", cascade={"persist"})
*/
protected $properties;
/**
* #param \Common\Entity\Property $property
*/
public function addProperty($property)
{
$property->setDomain($this);
$this->properties[] = $property;
}
/**
* #param string $name
* #param bool $default
* #return bool|string
*/
public function getPropertyValue($name, $default = false) {
/** #var Property $property */
foreach ($this->getProperties() as $property) {
if ($name == $property->getProperty()) {
return $property->getValue();
}
}
return $default;
}
/**
* #return mixed
*/
public function getProperties()
{
return $this->properties;
}
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain)
{
$this->domain = $domain;
}
/**
* #return mixed
*/
public function getSubdomain()
{
return $this->subdomain;
}
/**
* #param mixed $subdomain
*/
public function setSubdomain($subdomain)
{
$this->subdomain = $subdomain;
}
public function __construct() {
$this->properties = new ArrayCollection();
}
}
And in the end the code for Properties:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="property",options={"collate"="utf8_swedish_ci"})
*/
class Property {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer", options={"unsigned":true})
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Domain", fetch="EAGER", inversedBy="properties")
*/
protected $domain;
/**
* #ORM\Column(length=45,nullable=true)
*/
protected $property;
/**
* #ORM\Column(type="text",nullable=true)
*/
protected $value;
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain)
{
$this->domain = $domain;
}
/**
* #return mixed
*/
public function getProperty()
{
return $this->property;
}
/**
* #param mixed $property
*/
public function setProperty($property)
{
$this->property = $property;
}
/**
* #return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* #param mixed $value
*/
public function setValue($value)
{
$this->value = $value;
}
}

Embed a collection of forms - doctrine, symfony

I have two tables "RFQ" and "RFQitem". I can make form which can create RFQ with their title description and amount. And I can create RFQitem form which can create RFQitem with their title, description and amount.
Problems starts when I need to upgrade my RFQ form, so that I can make in it RFQitems which will saves in their table, but it need to be assigned to RFQ.
In symfony documentation is great example which actually works for me, but this is example is with task and their tags. So task there is with more than one attributes (name, description), but tags are only with one - name.
My RFQ entity with RFQItems looks like this:
/**
* #ORM\ManyToMany(targetEntity="RFQItem", cascade={"persist"})
* #ORM\JoinTable(name="rfq_item_title",
* joinColumns={#ORM\JoinColumn(name="rfq_item_title", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
protected $rfq_item_title;
/**
* #ORM\ManyToMany(targetEntity="RFQItem", cascade={"persist"})
* #ORM\JoinTable(name="rfq_item_description",
* joinColumns={#ORM\JoinColumn(name="rfq_item_description", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
protected $rfq_item_description;
/**
* #ORM\ManyToMany(targetEntity="RFQItem", cascade={"persist"})
* #ORM\JoinTable(name="rfq_item_amount",
* joinColumns={#ORM\JoinColumn(name="rfq_item_description", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
*/
protected $rfq_item_amount;
But I know that this is wrong, but how I make ManyToMany relation with RFQitem which have more than one attributes?
The best way to go is to have this two entities as you are actually doing, the father and the collection childs with the attributtes you like, but do not get fixated to the Symfony example. It's a theoretical OOP, has not relations defined, so I'm going to make my best try to paste a coherent example based on a Theater->Works collection:
class Theater
{
private $name;
private $id;
/**
* Set name
* #param string $name
* #return Theater
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get id
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #var \Doctrine\Common\Collections\Collection
*/
private $work;
/**
* Constructor
*/
public function __construct()
{
$this->work = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add work
*
* #param \Acme\RelationBundle\Entity\Work $work
* #return Theater
*/
public function addWork(\Acme\RelationBundle\Entity\Work $work)
{
$this->work[] = $work;
return $this;
}
/**
* Remove work
* #param \Acme\RelationBundle\Entity\Work $work
*/
public function removeWork(\Acme\RelationBundle\Entity\Work $work)
{
$this->work->removeElement($work);
}
/**
* Get work
* #return \Doctrine\Common\Collections\Collection
*/
public function getWork()
{
return $this->work;
}
}
Then the child entity Work:
class Work
{
// took out some comments to make it shorter
private $name;
private $description;
private $id;
/**
* Set name
* #param string $name
* #return Work
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Set description : And any others setters/getters with the attributes you want
* #param string $description
* #return Work
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get name
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Get description
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Get id
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __toString()
{
return (string) $this->getName();
}
}
The trick is to use this Collection in Doctrine and then make a two Form Types, the parent and the childs, something like this example below.
TheatherType Formtype includes the Work childs:
$buider->add('rowswork', 'collection', array(
'type' => new WorkChildType(),
'allow_add' => true,
'allow_delete' => true,
)
);
So there is one row with their Work childs that have their own WorkChildType with the attributes from the entity. It's like a form, with an embedded array collection of items, in your case an "RFQ" father form and "RFQitem" childs.

Serializing Entity Relation only to Id with JMS Serializer

I'm trying to serialize a entity relation with JMS Serializer.
Here is the Entity:
class Ad
{
/**
* #Type("string")
* #Groups({"manage"})
*
* #var string
*/
private $description;
/**
* #Type("Acme\SearchBundle\Entity\Country")
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*/
private $country;
/**
* #Type("string")
* #Groups({"manage"})
*
* #var string
*/
private $title;
/**
* Set description
*
* #param string $description
* #return Ad
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set country
*
* #param \Acme\SearchBundle\Entity\Country $country
* #return Ad
*/
public function setCountry($country)
{
$this->country= $country;
return $this;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
/**
* Set title
*
* #param string $title
* #return Ad
*/
public function setTituloanuncio($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
And the Entity of the relationship:
class Country
{
/**
* #Type("string")
* #Groups("manage")
*
* #var string
*/
private $id;
/**
* #Type("string")
* #Groups("admin")
*
* #var string
*/
private $description;
/**
* Set description
* #Groups("")
*
* #param string $description
* #return Country
*/
public function setDescripcionpais($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
}
I serialize the entity but I don't know how to convert the country attribute into a simple field.
I get this result in json:
{"description":"foo", "title":"bar", "country":{"id":"en"} }
But I want to get the id field of the country like this:
{"description":"foo", "title":"bar", "country": "en" }
It is possible with JMS Serializer?
Thank you.
[EDIT]
#VirtualProperty doesn't work.
Yes, you could use #VirtualProperty annotation:
/**
* #VirtualProperty
* #SerializedName("foo")
*/
public function bar()
{
return $this->country->getCode();
}
But be aware when it comes to deserialization:
#VirtualProperty This annotation can be defined on a method to
indicate that the data returned by the method should appear like a
property of the object.
> Note: This only works for serialization and is completely ignored
during deserialization.
Hope this helps...
Just to follow answered question:
If you don't like writing one method for each relation you have - just write your own handler. It's easy like
final class RelationsHandler
{
/**
* #var EntityManagerInterface
*/
private $manager;
/**
* RelationsHandler constructor.
*
* #param EntityManagerInterface $manager
*/
public function __construct(EntityManagerInterface $manager) { $this->manager = $manager; }
public function serializeRelation(JsonSerializationVisitor $visitor, $relation, array $type, Context $context)
{
if ($relation instanceof \Traversable) {
$relation = iterator_to_array($relation);
}
if (is_array($relation)) {
return array_map([$this, 'getSingleEntityRelation'], $relation);
}
return $this->getSingleEntityRelation($relation);
}
/**
* #param $relation
*
* #return array|mixed
*/
protected function getSingleEntityRelation($relation)
{
$metadata = $this->manager->getClassMetadata(get_class($relation));
$ids = $metadata->getIdentifierValues($relation);
if (!$metadata->isIdentifierComposite) {
$ids = array_shift($ids);
}
return $ids;
}
}
Register the Handler
jms_serializer.handler.relation:
class: MyBundle\RelationsHandler
arguments:
- "#doctrine.orm.entity_manager"
tags:
- { name: jms_serializer.handler, type: Relation, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation, direction: deserialization, format: json, method: deserializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: deserialization, format: json, method: deserializeRelation}
This allows you to replace virtual getter methods with `Type("Relation").
If you also want't to deserialize relation - you should tell each #Type("Relation") the classname (#Type("Relation<FQCN>")) which it should deserialize to or wrap the metadata driver with one which do it for you.
public function deserializeRelation(JsonDeserializationVisitor $visitor, $relation, array $type, Context $context)
{
$className = isset($type['params'][0]['name']) ? $type['params'][0]['name'] : null;
if (!class_exists($className, false)) {
throw new \InvalidArgumentException('Class name should be explicitly set for deserialization');
}
$metadata = $this->manager->getClassMetadata($className);
if (!is_array($relation)) {
return $this->manager->getReference($className, $relation);
}
$single = false;
if ($metadata->isIdentifierComposite) {
$single = true;
foreach ($metadata->getIdentifierFieldNames() as $idName) {
$single = $single && array_key_exists($idName, $relation);
}
}
if ($single) {
return $this->manager->getReference($className, $relation);
}
$objects = [];
foreach ($relation as $idSet) {
$objects[] = $this->manager->getReference($className, $idSet);
}
return $objects;
}
I know this has already been answered but you could also use #Accessor.
This probably (may, I can't be sure) work with deserialization too.
/**
* #Type("Acme\SearchBundle\Entity\Country")
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*
* #Serializer\Accessor(getter="getCountryMinusId",setter="setCountryWithId")
*/
private $country;
/**
* #return string|null
*/
public function getCountryMinusId()
{
if (is_array($this->country) && isset($this->country['id'])) {
return $this->country['id'];
}
return null;
}
/**
* #param string $country
* #return $this
*/
public function setCountryWithId($country)
{
if (!is_array($this->country)) {
$this->country = array();
)
$this->country['id'] = $country;
return $this;
}
You can use #Type and #Accessor annotations:
/**
* #Type("string")
* #Accessor(getter="serializeType",setter="setType")
*/
protected $type;
public function serializeType()
{
return $this->type->getId();
}
The author wants to keep the property name, which doesn't apply to the accepted answer. As far as I understood, the answer by ScayTrase would keep the original property name but has another disadvantage according to the comments: The related object will be fetched if you are using Doctrine ORM #ManyToOne, thus decreasing performance.
If you want to keep the original property name, you have to define the #VirtualProperty at class level and #Exclude the original property. Otherwise, the serialized property name will be derived from the getter method (countryId in this case):
/**
* #Serializer\VirtualProperty(
* "country",
* exp="object.getCountryId()",
* options={#Serializer\SerializedName("country")}
* )
*/
class Ad {
/**
* #Serializer\Exclude
*/
private $country;
public function getCountryId() {
return $this->country === null ? null : $this->country->getId();
}
}
Alternatively, you can #inline $country which will serialize its properties into the parent relation. Then you can #Expose the Country $id and set its #SerializedName to "country". Unlike Virtual properties, both serialization and deserialization will work for inline properties.
For this to work, you need to use the #ExclusionPolicy("All") on each class and judiciously #Expose the properties that you need in any of your groups. This is a more secure policy anyways.
/**
* #ExclusionPolicy("All")
*/
class Ad
{
//...
/**
* #Type("Acme\SearchBundle\Entity\Country")
*
* #Expose()
* #Inline()
* #Groups({"manage"})
*
* #var \Acme\SearchBundle\Entity\Country
*/
private $country;
//...
}
/**
* #ExclusionPolicy("All")
*/
class Country
{
//...
/**
* Get id
*
* #Expose()
* #Groups({"manage"})
* #SerializedName("country")
* #return string
*/
public function getId()
{
return $this->id;
}
}

Categories