Symfony Serializer: Deserialize with relation - php

I test the serializer component and try to do the following.
I have an Article entity:
class Article
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $title;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\OneToMany(targetEntity=Comment::class, mappedBy="article", orphanRemoval=true, cascade={"persist"})
*/
private $comments;
//getter/setter...
and a Comment entity :
class Comment
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $title;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity=Article::class, inversedBy="comments", cascade={ "persist" })
* #ORM\JoinColumn(nullable=false)
*/
private $article;
//getter/setter...
When a send a json to the controller:
{
"title": "My comment",
"content": "This is a comment",
"article": {
"id": 1
}
}
Article with id 1 exists
I would like the relationship with Article to be deserialized:
#[Route('', name: "comment_create", methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$comment = $this->serializer->deserialize($request->getContent(), Comment::class, 'json');
// $this->entityManager->persist($comment);
// $this->entityManager->flush();
return $this->json($comment, Response::HTTP_CREATED);
}
but the article is not linked.
{"id":null,"title":"My comment","content":"This is a comment","article":{"id":null,"title":null,"content":null,"comments":[]}}
Where is my mistake ? Can the symfony serializer do this ?

Try to use following for deserialization:
$comment = $this->serializer->deserialize($request->getContent(), 'App\Entity\Comment', 'json');

Related

Symfony Seralizer doesn't serialize OneToOne relation / Doctrin Proxy

I'm trying to serialize a Entity that has a OneToOne relation to a Image Entity.
When dumping the Entity I can see that the Image Entity is a Doctrine Proxy and not initialized.
It also doesn't show up in the JSON response when trying to access the Endpoint.
I've tried fetch="EAGER" with no success.
How do I go about serializing this to the "Expected Response" ?
Response:
[
{
"id": 7
}
]
Expected Response:
[
{
"id": 7,
"image": {
"webView": "someUrl"
}
}
]
Entity:
/**
* #ORM\Entity(repositoryClass=BatteryTypeRepository::class)
*/
class BatteryType
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*
* #Groups({"type"})
*/
private $id;
/**
* #ORM\OneToOne(targetEntity=Image::class, cascade={"persist", "remove"})
*
* #Groups({"type"})
*/
private $image;
/**
* #return Image|null
*/
public function getImage(): ?Image
{
return $this->image;
}
public function setImage(?Image $image): self
{
$this->image = $image;
return $this;
}
}
Image Entity:
/**
* #ORM\Entity(repositoryClass=ImageRepository::class)
* #ORM\EntityListeners({"App\EventSubscriber\ImageListener"})
*/
class Image
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"type"})
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $filename;
/**
* Auto generated by entity listener
* #var string
*/
private $tempFilename;
/**
* #var File
* #Assert\Image()
* #Assert\NotBlank()
*/
private $file;
/**
* #var string
* #Groups({"type"})
*/
private $webView;
/** Getters & Setters */
}
ImageListener:
class ImageListener
{
/**
* #var S3FileManagerInterface
*/
private S3FileManagerInterface $fileManager;
public function __construct(S3FileManagerInterface $fileManager)
{
$this->fileManager = $fileManager;
}
public function postLoad(Image $image, LifecycleEventArgs $args): void
{
$image->setWebView(
$this->fileManager->getUrl($image->getFilename())
);
if (!$image->getTempFilename()) {
$image->setTempFilename($image->getFilename());
}
}
}
Controller:
public function getBatteryTypes(BatteryTypeRepository $batteryTypeRepository): Response
{
$batteryTypes = $batteryTypeRepository->findAll();
$view = $this->view($batteryTypes, Response::HTTP_OK);
$view->getContext()->setGroups(["type"]);
return $this->handleView($view);
}
In your Image entity, you have to add the #Groups({"type"}) annotation for the webView attribute.
My Problem apparently was just cache related

DenormalizerInterface denormalize Cache key contains reserved characters {}()/\#: Entities with associations

I have this Exception when try to convert array to object with associations.
In Contact entity is construct with Company entity.
EXCEPTION:
Cache key "pAppBundle\Entity\Companyid" contains reserved characters {}()/\#:
Controller:
class ContactController extends AbstractController
{
public function newAction(Request $request, Company $company, DenormalizerInterface $denormalizer)
{
$data = $request->get('contact');
$data['company'] = $company;
$denormalizer->denormalize($data, Contact::class);
}
}
Entity Company:
class Company {
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string", name="name")
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Contact", mappedBy="company", cascade={"ALL"})
*/
private $contacts;
}
Entity Contact:
class Contact {
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string", name="text")
*/
private $text;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Company", inversedBy="contacts")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
*/
private $company;
public function __construct(Company $company)
{
$this->company = $company;
$this->files = new \Doctrine\Common\Collections\ArrayCollection();
}
}

Symfony 3 Doctrine undefined index

I'm trying to fetch an object from MySQL database using doctrine. It has one-to-one relation with other one. I'm getting this error: Notice: Undefined index: id
What I should correct to make this working?
My fetch code is very simple, I try to fetch all objects:
$emArt = $this->getDoctrine()->getRepository(Article::class);
$articles = $emArt->findAll();
Model is as following:
Article.php
/**
* #ORM\Entity
* #ORM\Table(name="Article")
*/
class Article
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $ID;
/**
* #ORM\OneToOne(targetEntity="ArticleTypes", inversedBy="articleTypeId")
* #ORM\JoinColumn(name="articleTypeId", referencedColumnName="id")
*/
private $articleType;
/**
* #ORM\Column(name="articleName", type="text")
*/
private $articleName;
/**
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #ORM\Column(name="image", type="string")
* #Assert\File(mimeTypes={ "image/png" })
*/
public $image;
public function getArticleImage()
{
return $this->image;
}
public function setArticleImage($newImage)
{
$this->image = $newImage;
}
public function getArticleContent()
{
return $this->content;
}
public function setArticleContent($name)
{
$this->content = $name;
}
public function getArticleName()
{
return $this->articleName;
}
public function setArticleName($name)
{
$this->articleName = $name;
}
public function getArticleType()
{
return $this->articleType;
}
public function setArticleType($type)
{
$this->articleType = $type;
}
}
ArticleTypes.php
/**
* #ORM\Entity
* #ORM\Table(name="ArticleTypes")
*/
class ArticleTypes
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\OneToOne(targetEntity="Article", mappedBy="articleTypeId")
*/
private $ID;
/**
* #ORM\Column(name="articleType", type="text")
*/
private $articleType;
public function getArticleType()
{
return $this->articleType;
}
public function setArticleType($newType)
{
$this->articleType = $newType;
}
}
It's a PHP Notice no ? You should have also the line number in a file with the error ?
But I saw errors in your doctrine mapping too : You can't use doctrine annotation like this with fields, you should link your relationship to objects :
/**
* #ORM\Entity
* #ORM\Table(name="Article")
*/
class Article
{
/**
* #ORM\OneToOne(targetEntity="ArticleTypes", inversedBy="article")
* #ORM\JoinColumn(name="articleTypeId", referencedColumnName="id")
*/
private $articleType;
....
}
And :
/**
* #ORM\Entity
* #ORM\Table(name="ArticleTypes")
*/
class ArticleTypes
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Article $article
* #ORM\OneToOne(targetEntity="Article", mappedBy="articleType")
*/
private $article;
....
}
Even if you have a one to one relationship you can have different ids.

Doctrine 2 removes parent row

I have defined two database entities
Project:
use Doctrine\ORM\Mapping as ORM;
use \Kdyby\Doctrine\Entities\MagicAccessors;
/**
* #ORM\Entity
*/
class Project extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
public $id;
/**
* #ORM\Column(type="string")
*/
public $title;
/**
* #ORM\Column(type="text")
* #var string
*/
public $description;
/**
* #ORM\Column(type="datetime", nullable=true)
* #var DateTime
*/
public $created;
/**
* #ORM\Column(type="boolean")
* #var string
*/
public $public;
/**
* #ORM\Column(type="blob")
* #var string
*/
public $thumbnail;
public function __construct()
{
$this->created = new \DateTime();
$this->public = 0;
}
}
and Personage:
use Doctrine\ORM\Mapping as ORM;
use \Kdyby\Doctrine\Entities\MagicAccessors;
/**
* #ORM\Entity
*/
class Personage extends \Kdyby\Doctrine\Entities\BaseEntity
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
public $id;
/**
* #ORM\Column(type="string")
*/
public $name;
/**
* #ORM\Column(type="text", nullable=true)
* #var string
*/
public $shortDescription;
/**
* #ORM\Column(type="text", nullable=true)
* #var string
*/
public $playerDescription;
/**
* #ORM\Column(type="datetime")
* #var DateTime
*/
public $created;
/**
* #ORM\Column(type="integer")
* #var string
*/
public $creator;
/**
* #ORM\Column(type="boolean", options={"default" = false})
* #var boolean
*/
public $inGraph;
/**
* #ORM\Column(type="boolean", nullable=false, options={"default" = 0})
* #var boolean
*/
public $deleted;
/**
* #ORM\ManyToOne(targetEntity="Project", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="project", referencedColumnName="id", onDelete="CASCADE")
* #var string
*/
public $project_id;
public function __construct()
{
$this->deleted = 0;
$this->inGraph = 0;
}
}
Every Personage entity belongs to some project. But whenever I try to remove Personage either this way:
public function removeChar($id)
{
$a = $this->EntityManager->find('App\Personage', $id);
if($a->deleted)
{
$this->EntityManager->remove($a);
$this->EntityManager->flush();
}
}
or using dql. It removes the whole project row as well. What i want is whenever project is removed it removes all characters corresponding but NOT other way around.
I tried to remove
cascade={"persist", "remove"}
part from Project entity declaration (and updating database via console) but it said everything was in sync and did nothing. So I removed whole database and build it without cascade={"persist", "remove"}, but the problem was not solved.
remove onDelete="CASCADE" from
#ORM\JoinColumn(name="project", referencedColumnName="id", onDelete="CASCADE")
and add
#ORM\OneToMany(targetEntity="Personage", mappedBy="project_id",cascade={"remove"})
private $personages;
in the project entity.

Persist a clone

In this problem Deep Cloning I thought my issue was due to a deep/shallow copy.
I have vainly tested clone() and unserialize(serialize()) methods.
So I tried to write my own clone function using all setters/getters and then I realized what was really my issue, a persisting one.
The fact is I already succeeded in persisting a clone of my entity, in another context.
The main difference between my two situations is that in one case my original object is already managed by doctrine (this is the case where i'm blocked), and in the second case, my original object is just persisted, I don't have called flush() yet (and it's works fine).
So this is the situation when persist do not persist the many to many relations :
public function duplicateCourseAction(Request $request) {
if ($this->getRequest()->isXmlHttpRequest() == false) {
return new Response("Bad request", 405);
}
$em = $this->getDoctrine()->getManager();
$parameters = $request->request->all();
$course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
$duplicate = clone $course;
$duplicate->setId(null);
$duplicate->setDate(new \DateTime($parameters['date']));
$em->persist($duplicate);
$em->flush();
return new Response("200");
}
And this is the situation whe it's works like a charm
$em->persist($obj);
while ($new_date < $up_to) {
if ($this->isAvailable($holidays, $new_date)) {
$new_course = clone $obj;
$new_course->setDate($new_date);
$new_course->setUuid($uuid);
$new_date = clone $new_date;
$em->persist($new_course);
}
$new_date->modify($modifier);
}
$em->flush();
Why is it working for only one situation ? There are almost identical...
EDIT 1 : Entities Mapping
-Course :
class Course {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=50, unique=true, nullable=true)
*/
protected $name;
/**
* #ORM\JoinColumn(onDelete="CASCADE")
* #ORM\ManyToOne(targetEntity="Ent\HomeBundle\Entity\Campus", inversedBy="courses")
* #Assert\NotBlank
**/
protected $campus;
/**
* #ORM\JoinColumn(onDelete="CASCADE")
* #ORM\ManyToOne(targetEntity="Ent\HomeBundle\Entity\Room", inversedBy="courses")
* #Assert\NotBlank
**/
protected $room;
/**
* #ORM\ManyToMany(targetEntity="Ent\UserBundle\Entity\User", inversedBy="courses", cascade={"persist"})
* #ORM\JoinTable(name="course_teacher",
* joinColumns={#ORM\JoinColumn(name="course_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="teacher_id", referencedColumnName="id", onDelete="CASCADE")}
* )
* #Assert\NotBlank
*/
private $teachers;
/**
* #ORM\JoinColumn(onDelete="CASCADE")
* #ORM\ManyToOne(targetEntity="Matter", inversedBy="courses")
* #Assert\NotBlank
**/
protected $matter;
/**
* #ORM\JoinColumn(onDelete="CASCADE")
* #ORM\ManyToOne(targetEntity="\Ent\UserBundle\Entity\Grade", inversedBy="courses")
* #Assert\NotBlank
**/
protected $grade;
/**
* #ORM\Column(type="datetime")
* #Assert\NotBlank
**/
protected $date;
/**
* #ORM\Column(type="time")
* #Assert\NotBlank
**/
protected $duration;
/**
* #ORM\Column(type="string", length=30, nullable=true)
*/
protected $uuid;
/**
* #ORM\ManyToMany(targetEntity="Ent\TimeBundle\Entity\Course", mappedBy="courses")
* #Exclude
*/
protected $alerts;
public function __toString() {
if (empty($this->getName())) {
$string = $this->getMatter().' - '.$this->getRoom().' - ';
foreach ($this->getTeachers() as $count => $teacher) {
$string = $string . $teacher;
if ($count < count($this->getTeachers()) - 1) {
$string = $string . ', ';
}
}
return $string;
} else {
return $this->getName().' - '.$this->getRoom();
}
}
/**
* Constructor
*/
public function __construct() {
$this->teachers = new ArrayCollection();
$this->alerts = new ArrayCollection();
}
public function __clone() {
// $this->id = null;
// $this->teachers = clone $this->teachers;
}
}
-User (Teacher) :
class User implements UserInterface, \Serializable {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank
*/
protected $firstName;
/**
* #ORM\Column(type="string", length=30)
* #Assert\NotBlank
*/
protected $lastName;
/**
* #ORM\Column(type="string", length=70, unique=true)
* #Assert\NotBlank
*/
protected $username;
/**
* #Gedmo\Slug(fields={"username"}, updatable=false)
* #ORM\Column(length=50, unique=true)
*/
protected $slug;
/**
* #ORM\Column(type="string", length=32)
* #Exclude
*/
protected $salt;
/**
* #ORM\Column(type="string", length=40)
* #Exclude
*/
protected $password;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $picture_path;
/**
* #Assert\File(maxSize="10M", mimeTypesMessage="Please upload a valid Image")
*/
protected $picture;
/**
* #ORM\Column(type="string", length=60, unique=true)
* #Exclude
* #Assert\NotBlank
*/
protected $email;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
protected $isActive;
/**
* #ORM\ManyToOne(targetEntity="Group", inversedBy="users")
* #ORM\JoinColumn(name="role_group", referencedColumnName="role", onDelete="CASCADE")
*/
protected $group;
/**
* #ORM\ManyToMany(targetEntity="Ent\HomeBundle\Entity\Campus", inversedBy="users")
* #Exclude
**/
protected $campuses;
/**
* #ORM\OneToMany(targetEntity="\Ent\NewsBundle\Entity\News", mappedBy="user")
* #Exclude
*/
protected $news;
/**
* #ORM\ManyToMany(targetEntity="\Ent\TimeBundle\Entity\Matter", inversedBy="teachers", cascade={"persist"})
* #ORM\JoinTable(name="user_matter",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="matter_id", referencedColumnName="id", onDelete="CASCADE")}
* )
*/
protected $matters;
/**
* #ORM\ManyToMany(targetEntity="Ent\UserBundle\Entity\Grade")
* #Assert\NotBlank
* #Exclude
**/
protected $grades;
/**
* #ORM\ManyToMany(targetEntity="Ent\TimeBundle\Entity\Course", mappedBy="teachers")
* #Exclude
**/
protected $courses;
/**
* #ORM\OneToMany(targetEntity="\Ent\TimeBundle\Entity\Alert", mappedBy="teacher")
* #Exclude
**/
protected $alerts;
protected $temp;
public function __construct() {
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
public function __toString() {
return $this->getFullName();
}
}
EDIT 2 : Solution
Thanks to Paul Andrieux this is the function we made to clone my object :
public function course_deep_clone($course) {
$em = $this->getDoctrine()->getManager();
$clone = clone $course;
$clone->setTeachers(array());
$teachers = $course->getTeachers();
foreach ($teachers as $teacher) {
$clone->addTeacher($teacher);
}
return $clone;
}
Thing is that ManyToMany related entities are not cloned, try that:
public function duplicateCourseAction(Request $request) {
if ($this->getRequest()->isXmlHttpRequest() == false) {
return new Response("Bad request", 405);
}
$em = $this->getDoctrine()->getManager();
$parameters = $request->request->all();
$course = $em->getRepository('EntTimeBundle:Course')->findOneById($parameters['id']);
$duplicate = clone $course;
$teachers = $course->getTeachers();
$duplicate->setTeachers($teachers);
$duplicate->setId(null);
$duplicate->setDate(new \DateTime($parameters['date']));
$em->persist($duplicate);
$em->flush();
return new Response("200");
}
This way, you are persisting new relationship and new join table between you two entities
EDIT:
Maybe its a cascading problem, what gives you this ? :
$teachers = $course->getTeachers();
foreach ($teachers as $teacher) {
$teacher->addCourse($duplicate);
$em->persist($teacher);
}

Categories