Why does Doctrine update related entities, after JMS deserialize - symfony

I'm using Doctrine+JMSserializer in Symfony2 project, just found a problem that related entities and their data could be compromised during typical JMS "deserialize" and Doctrine "persist" operation.
$data = [
'locale' => 'en',
'name' => $this->faker->sentence(),
'content' => $this->faker->text(),
'subject' => [
'id' => $page->getId(),
'name' => 'new name' // <-- compromised property
]
];
$rawPayload = json_encode($data);
$entity = $this->serializer->deserialize(
$rawPayload,
$resolvedClass,
'json'
);
and after typical persist operation for new $entity related Page entity name, being change - so its a problem.
$this->getEntityManager()->persist($entity);
$this->getEntityManager()->flush();
The goal is to set page Id for the review, but prevent change for other properties, otherwise Page entity can be compromised. I tried to solve this issue changing annotations, doctrine settings, etc..
Any ideas how to solve this issue in natural way?
Review and Page entites:
use Aisel\ReviewBundle\Entity\Review as BaseReview;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use JMS\Serializer\Annotation as JMS;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Review
*
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="aisel_page_review")
* #ORM\Entity(repositoryClass="Aisel\ResourceBundle\Repository\CollectionRepository")
* #JMS\ExclusionPolicy("all")
*/
class Review extends BaseReview
{
/**
* #var Page
* #Assert\NotNull()
* #ORM\ManyToOne(targetEntity="Aisel\PageBundle\Entity\Page", inversedBy="pages")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="page_id", referencedColumnName="id", nullable=false)
* })
* #JMS\Type("Aisel\PageBundle\Entity\Page")
* #JMS\Expose
* #JMS\MaxDepth(2)
*/
private $subject;
/**
* #return Page
*/
public function getSubject()
{
return $this->subject;
}
/**
* #param Page $subject
*/
public function setSubject($subject)
{
$this->subject = $subject;
}
}
<?php
namespace Aisel\PageBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use JMS\Serializer\Annotation as JMS;
use Aisel\ResourceBundle\Domain\UrlInterface;
use Aisel\PageBundle\Entity\Node;
use Aisel\PageBundle\Entity\Review;
use Aisel\UserBundle\Entity\User;
use Aisel\ResourceBundle\Domain\IdTrait;
use Aisel\ResourceBundle\Domain\UpdateCreateTrait;
use Aisel\ResourceBundle\Domain\MetaTrait;
use Aisel\ResourceBundle\Domain\LocaleTrait;
use Aisel\ResourceBundle\Domain\StatusTrait;
use Aisel\ResourceBundle\Domain\NameTrait;
use Aisel\ResourceBundle\Domain\ContentTrait;
use Aisel\ResourceBundle\Domain\CommentStatusTrait;
/**
* Page
*
* #author Ivan Proskuryakov <volgodark#gmail.com>
*
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="aisel_page")
* #ORM\Entity(repositoryClass="Aisel\ResourceBundle\Repository\CollectionRepository")
* #JMS\ExclusionPolicy("all")
*/
class Page implements UrlInterface
{
use IdTrait;
use NameTrait;
use ContentTrait;
use LocaleTrait;
use StatusTrait;
use CommentStatusTrait;
use MetaTrait;
use UpdateCreateTrait;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="Aisel\PageBundle\Entity\Node")
* #ORM\JoinTable(
* name="aisel_page_page_node",
* joinColumns={#ORM\JoinColumn(name="page_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="node_id", referencedColumnName="id")}
* )
* #JMS\Type("ArrayCollection<Aisel\PageBundle\Entity\Node>")
* #JMS\Expose
* #JMS\MaxDepth(2)
* #JMS\Groups({"collection","details"})
*/
private $nodes;
/**
* #var ArrayCollection<Aisel\PageBundle\Entity\Review>
* #ORM\OneToMany(targetEntity="Aisel\PageBundle\Entity\Review", mappedBy="subject", cascade={"remove"})
* #ORM\OrderBy({"createdAt" = "DESC"})
* #JMS\Expose
* #JMS\MaxDepth(2)
* #JMS\Type("ArrayCollection<Aisel\PageBundle\Entity\Review>")
* #JMS\Groups({"collection","details"})
*/
private $reviews;

Related

Default API Filters Failing when referencing nested properties of related entities

I am configuring a series of ApiFilter classes from the Api-platform stack in a Symfony 4 application. I am finding that any filters using a nested property from an entity relationship takes no effect. Please see my example code below, would really appreciate any assistance with why it may not be working.
The entity I am trying to filter on is:
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiProperty;
/**
* PostAuthor
* #ApiResource()
* #ORM\Table(name="POST_AUTHOR", indexes={#ORM\Index(name="IDX_CF3397D94A7000A0", columns={"IDENTITY_ID"})})
* #ORM\Entity(repositoryClass="App\Repository\PostAuthorRepository")
*/
class PostAuthor
{
/**
* #var string
*
* #ORM\Column(name="USER_ID", type="string", length=255, nullable=false)
* #ORM\Id
*/
private $userId;
/**
* #var string
*
* #ORM\Column(name="USERNAME", type="string", length=255, nullable=false)
*/
private $username;
And the collection I am filtering is:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiFilter;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\NumericFilter;
use Doctrine\ORM\Mapping as ORM;
/**
* PostHeader
* #ORM\Table(name="POST_HEADER", indexes={#ORM\Index(name="IDX_1CEEE7D0B65E5FF8", columns={"EMOTION_REF_ID"}), #ORM\Index(name="IDX_1CEEE7D08AFAAB14", columns={"AUTHOR_ID"})})
* #ORM\Entity(repositoryClass="App\Repository\PostHeaderRepository")
* #ApiResource(
* normalizationContext={"groups"={"postheader:read"}},
* denormalizationContext={"groups"={"postheader:write"}},
* )
* #ApiFilter(SearchFilter::class, properties={"author.userId": "partial", "author.username": "partial"})
*/
class PostHeader
{
/**
* #var PostAuthor
*
* #ORM\ManyToOne(targetEntity="PostAuthor")
* #ORM\JoinColumns({#ORM\JoinColumn(name="AUTHOR_ID", referencedColumnName="USER_ID")})
* #Groups({"postheader:read"})
*/
private $author;
I just test on a new project with the same classes than you and it works. I can filter by author.userId.
Please give us your testing request. And the error message ;).

Get FosUser authenticated to persist into Easy Admin Entity ( USER LOG)

I have An entity Product Manged By EasyAdmin, i ALSO USE fos UserbUNDLE for user Management.
Everything works fine but when i add a new user in easyadmin i have a dropdown of user but i want to have automatically the authenticated user.
I have defined an one to Many relation between User and Product in an attribut of the class Product.
/**
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
Product Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
* #Vich\Uploadable
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="reference", type="string", length=150)
*/
private $reference;
/**
* #ORM\ManyToOne(targetEntity="Type")
* #ORM\JoinColumn(name="type_id", referencedColumnName="id")
*/
private $type;
/**
* #ORM\Column(type="boolean")
*/
public $status;
/**
* #var string
*
* #ORM\Column(name="titre", type="string", length=150)
*/
private $titre;
User :
<?php
namespace AppBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
How to get it automatically?
Can we have more details ? Both entities and controller because i think your not using the good targetEntity
i had quite the same issue,i mean,in some cases i was needing to get ONLY THE CURRENT LOGGED USER INFO INSIDE MY SELECT, and sometimes i was needing to get the full list of users from the database
maybe the solution i found could help you:
Get the logged user info in the easyadmin select
to make it simple you have to override the easyadmin formbuilder and ask only the currently logged user info
$formBuilder->add('user', EntityType::class, array(
'class' => 'AppBundle:User',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('u')
->where('u.id = :id')->setParameter('id',$this->getUser()->getId());
},
));
return $formBuilder;

filtering results based on two ManyToOne relations in Symfony2

In the example below $type can be both 'brand' or 'category', and $slug will be either a brand or a category.
How would I approach this when I want to filter my results on a category and a brand at the same time?
public function getGroupAction($slug, $type = null, $grouped = true)
{
$group = $this->getDoctrine()
->getRepository('AudsurShopBundle:'.$type)
->findOneBy(array( 'name' => $slug ))
->getProducts();
return $this->render('AudsurShopBundle:Default:productOverview.html.twig', array(
'group' => $group
)
);
}
To do what you want to do, you have to use Class table inheritance with the system of a discriminator column.
For your example, create the following entity :
<?php
namespace ...
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discriminator", type="string")
* #ORM\DiscriminatorMap({"category" = "Category", "brand" = "Brand"})
*/
abstract class NameHolder
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
// Getters and setters
}
and then make your 2 entities inherit this class:
<?php
namespace ...;
use Doctrine\ORM\Mapping as ORM;
/**
* FirstChild
*
* #ORM\Table()
* #ORM\Entity
*/
class Category extends NameHolder
{
// all methods and properties except from the "name"
}
and:
<?php
namespace ...;
use Doctrine\ORM\Mapping as ORM;
/**
* FirstChild
*
* #ORM\Table()
* #ORM\Entity
*/
class Brand extends NameHolder
{
// all methods and properties except from the "name"
}
So now you can make a query like this:
$group = $this->getDoctrine()
->getRepository('AudsurShopBundle:NameHolder')
->findOneBy(array('name' => $slug))
->getProducts();
This will return an array collection with both Brand and Category entities.
However I am not sure a NameHolder class really makes sense. An other solution would be to make separate queries for both entities without changing any of your entities, but that's not what you seem to be looking for.

Validating arraycollection in symfony2

I'm new to Symfony2 + Doctrine and I´m looking for a way to validate the uniqueness in an Arraycollection. May be it is already answered question but I can´t figure how resolve it.
I`ve a Relevamientosserviciosprestador class with a Callback:
namespace Prestadores\PrincipalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\ExecutionContext;
/**
* Prestadores\PrincipalBundle\Entity\Relevamientosserviciosprestador
*
* #ORM\Table(name="relevServiciosPrestador")
* #ORM\Entity(repositoryClass="Prestadores\PrincipalBundle\Repository\RelevamientosserviciosprestadorRepository")*
* #Assert\Callback(methods={"sonUnicosLosTiposDeReclamoq"})
*/
class Relevamientosserviciosprestador
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
....
....
/**
* #ORM\OneToMany(targetEntity="Atencionusuarioreclamo", mappedBy="relevamiento_id", cascade={"persist"})
* #Assert\Valid
*/
private $reclamos;
....
....
public function __construct()
{
$this->personal = new ArrayCollection();
$this->reclamos = new ArrayCollection();
}
....
....
/*Acá intentaremos validar si los tipos de reclamo que se están cargando son únicos para ese relevamiento*/
public function sonUnicosLosTiposDeReclamoq(ExecutionContext $context)
{
foreach ($this->reclamos as $reclamo){
/*Here, I get all entities, not only those related to a Relevamientosserviciosprestador*/
var_dump($reclamo->gettiporeclamo()->getnombre());
}
}
}
And the Atencionusuarioreclamo entity:
namespace Prestadores\PrincipalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Prestadores\PrincipalBundle\Entity\Atencionusuarioreclamo
*
* #ORM\Table(name="atencionUsuarioReclamo")
* #ORM\Entity
*/
class Atencionusuarioreclamo
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var Atencionusuariosede
*
* #ORM\ManyToOne(targetEntity="Atencionusuariosede")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="nroSede", referencedColumnName="id")
* })
*/
private $nrosede;
/**
* #var relevamiento_id
*
* #ORM\ManyToOne(targetEntity="Relevamientosserviciosprestador")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="relevamiento_id", referencedColumnName="id")
* })
*/
private $relevamiento_id;
/**
* #var Prmreclamotipo
*
* #ORM\ManyToOne(targetEntity="Prmreclamotipo")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="tipoReclamo", referencedColumnName="id")
* })
* #Assert\NotBlank()
*/
private $tiporeclamo;
....
....
....
....
}
I want to chek uniqueness of tiporeclamo in a given sede and relevamiento_id
I create or edit a Relevamientosserviciosprestador using a Form which has a sub form collection for "Atencionusuarioreclamo" entities. On submit, the callback for Relevamientosserviciosprestador executes but $this->reclamos has all saved entities not only those related to the Relevamientosserviciosprestador what I´m editing.
Is this the expected behauvoir or I`m missing something?
I´ve also tested the approach mentioned in How to validate unique entities in an entity collection in symfony2
but, again, it checks all entities.
I´ve also read Doctrine2 ArrayCollection but I cant´t understand if it resolve the problem.
Please, can you tell me how do you manage uniqueness in your ArrayCollection before persist it?
I´m sorry for my poor english
Thanks in advance
Ivan
php's array array_unique ( array $array [, int $sort_flags = SORT_STRING ] )
combined with ArrayCollection->toArray() and ArrayCollection->__construct()
In your case:
$Atencionusuarioreclamo =
new ArrayCollection(array_unique($Atencionusuarioreclamo->toArray());

Doctrine2 - OneToMany/ManyToOne Bidirectional not working

Hi everyone and thanks for help,
I'm currently dealing with a problem while I want to make an OneToMany/ManyToOne Bidirectional Relationship with Doctrine2 (& Symfony2).
Here are my two classes : User (which extends FOSUser) and collection which represents a collection of videos.
Of course, a user can have several collections, but a collection is only related to one user.
/* COLLECTION */
namespace Com\ComBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Com\ComBundle\Entity\Collection
*
* #ORM\Table(name="collection")
* #ORM\HasLifecycleCallbacks
*/
class Collection
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Com\UserBundle\Entity\User", inversedBy="collections")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* Set user
*
* #param Com\UserBundle\Entity\User $user
* #return Collection
*/
public function setUser(\Com\UserBundle\Entity\User $user) {
$this->user = $user;
return $this;
}
/**
* Get user
*
* #return Com\UserBundle\Entity\User User
*/
public function getUser() {
return $this->user;
}
}
And user,
/* USER */
namespace Com\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/*
* #ORM\OneToMany(targetEntity="Com\ComBundle\Entity\Collection", mappedBy="user")
*/
private $collections;
public function __construct()
{
parent::__construct();
$this->collections = \Doctrine\Common\Collections\ArrayCollection();
}
}
When I use the doctrine:generate:entities command, it does not generate the methods relative to $collections (get/add/remove), and even if I wrote it myself, it does not work.
getCollections() return NULL for example.
What am I missing in this code ?

Resources