Doctrine rewrites relationship with eager fetching value after findBy - php

I have a simple entity
/**
* #ORM\Entity(repositoryClass="...\Repository\UserTestRepository")
* #ORM\Table(name="users", uniqueConstraints={
* #ORM\UniqueConstraint(name="U_email", columns={"email"})
* })
* #UniqueEntity("email", message="Email is already used!")
*/
class UserTest
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=180)
* #Assert\NotBlank()
* #Assert\Email()
*/
protected $email;
/**
* #var string
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\NotBlank()
*/
protected $jobTitle;
/**
* #var Company
* #ORM\ManyToOne(targetEntity="...\Entity\Company", fetch="EAGER")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id", nullable=true, onDelete="SET NULL")
*/
private $company;
public function getId()
{
return $this->id;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getJobTitle()
{
return $this->jobTitle;
}
public function setJobTitle($jobTitle)
{
$this->jobTitle = $jobTitle;
return $this;
}
public function setCompany(...\Entity\Company $company = null)
{
$this->company = $company;
return $this;
}
public function getCompany()
{
return $this->company;
}
}
and controller
$repo = $this->getDoctrine()->getRepository('..\Entity\UserTest');
$user = $repo->find(519);
dump($user);
$user->setJobTitle('new value');
$user->setCompany(null);
dump($user);
$repo->findBy(['email' => 'test#test.com']);
dump($user);
1-st dump, original
2-nd dump, after changes without flush
3-rd dump, after findBy, jobTitle - with new value, company - original value, changes were lost
Is it normal doctrine behaviour or is it a bug? I use doctrine/orm v2.5.11. Was it fixed in newer versions?
Any help, pls

It's Doctrine normal behaviour when looking for unflushed entity through repository's findBy(). Please refer to https://github.com/doctrine/orm/issues/5092 to get more info.

Related

Relation One to Many return empty object

I have a Evaluation entity which has one Product and Product which can have several Evaluations. I'm trying to fetch one Product and to get the list of Evaluations associated with my entity
Produit.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use phpDocumentor\Reflection\Types\This;
/**
* Produit
*
* #ORM\Table(name="produit", indexes={#ORM\Index(name="fk_idcatedel", columns={"idCategorie"})})
* #ORM\Entity
*/
class Produit
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string|null
*
* #ORM\Column(name="libelle", type="string", length=20, nullable=true)
*/
private $libelle;
/**
* #var float|null
*
* #ORM\Column(name="prix", type="float", precision=10, scale=0, nullable=true)
*/
private $prix;
/**
* #var string|null
*
* #ORM\Column(name="description", type="string", length=50, nullable=true)
*/
private $description;
/**
* #var int
*
* #ORM\Column(name="qt", type="integer", nullable=false)
*/
private $qt;
/**
* #var string|null
*
* #ORM\Column(name="img", type="string", length=255, nullable=true)
*/
private $img;
/**
* #var \Categorie
*
* #ORM\ManyToOne(targetEntity="Categorie")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="idCategorie", referencedColumnName="id")
* })
*/
private $idcategorie;
/**
* #ORM\OneToMany(targetEntity="Evaluation", mappedBy="idProduit")
*/
private $rates;
public function __construct()
{
$this->rates = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getLibelle(): ?string
{
return $this->libelle;
}
public function setLibelle(?string $libelle): self
{
$this->libelle = $libelle;
return $this;
}
public function getPrix(): ?float
{
return $this->prix;
}
public function setPrix(?float $prix): self
{
$this->prix = $prix;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getQt(): ?int
{
return $this->qt;
}
public function setQt(int $qt): self
{
$this->qt = $qt;
return $this;
}
public function getImg(): ?string
{
return $this->img;
}
public function setImg(?string $img): self
{
$this->img = $img;
return $this;
}
public function getIdcategorie(): ?Categorie
{
return $this->idcategorie;
}
public function setIdcategorie(?Categorie $idcategorie): self
{
$this->idcategorie = $idcategorie;
return $this;
}
/**
* #return Collection|Evaluation[]
*/
public function getRates(): Collection
{
return $this->rates;
}
}
Evaluation.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Evaluation
*
* #ORM\Table(name="evaluation", indexes={#ORM\Index(name="fk_idprodevaldel", columns={"id_produit"}), #ORM\Index(name="fk_iduser", columns={"id_user"})})
* #ORM\Entity
*/
class Evaluation
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="note", type="integer", nullable=false)
*/
private $note;
/**
* #var \Produit
*
* #ORM\ManyToOne(targetEntity="Produit", inversedBy="rates")
* #ORM\JoinColumn(name="id_produit", referencedColumnName="id")
*/
private $idProduit;
/**
* #var \Compte
*
* #ORM\ManyToOne(targetEntity="Compte")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_user", referencedColumnName="email")
* })
*/
private $idUser;
public function getId(): ?int
{
return $this->id;
}
public function getNote(): ?int
{
return $this->note;
}
public function setNote(int $note): self
{
$this->note = $note;
return $this;
}
public function getIdProduit(): ?Produit
{
return $this->idProduit;
}
public function setIdProduit(?Produit $idProduit): self
{
$this->idProduit = $idProduit;
return $this;
}
public function getIdUser(): ?Compte
{
return $this->idUser;
}
public function setIdUser(?Compte $idUser): self
{
$this->idUser = $idUser;
return $this;
}
}
The database
In my controller I succeed to get informations from the products but rates are empty
$produits = $this->getDoctrine()
->getRepository(Produit::class)
->find(1);
dump($produits);
$rates = $produits->getRates();
dump($rates); // #collection: ArrayCollection is empty
The Output :
The collection is not yet initialized due to lazy loading, and rightfully so. If you don't access at least to an element in the collection, it's pointless to load the whole collection because doctrine can safely assume you'll "discard" it. As soon as you access an element (either by looping onto collection or getting a specific element), the collection will be initialized and you have all items.
Another way is to use an EAGER fetch that will load the whole collection in the hydration phase. I would not reccomend it however, unless you're sure that everytime you load a Produit, you need this collection "ready". Even in the latter case, I would handle the collection "manually" as I recommend not to lose control on it (let's pretend you have A LOT of element inside it).
Read more about proxies and association, here

Access User Scores on Many-to-One Relationship using Symfony 3

I'm creating an app that keeps user scores in the database based on questions they solve. I have User, Problem and Submission entities. For all of them I have One-to-Many relationship from the User entity. The problem entity has the point field that holds the score of a problem. I'm trying to retrieve all users with their total points.
Here is my controller;
$userService = $this->container->get('userservice');
$users = $userService->findAll();
foreach ($users as $user){
$usersWPoints = $user->getSubmissions()->getProblemId()->getPoints;
}
However this returns the following error;
Attempted to call an undefined method named "getProblemId" of class "Doctrine\ORM\PersistentCollection".
Here are my models
User.php
/**
* Class User
* #package AppBundle\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
* #ORM\Table(name="user")
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity(fields="username", message="Email already taken")
*
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank(message="Please enter a valid email address")
* #Assert\Email()
*/
private $username;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank(message="Please enter a valid email address")
*/
private $usrname;
/**
* #Assert\NotBlank()
* #Assert\Length(max=4096)
*/
private $plainPassword;
/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt.
*
* #ORM\Column(type="string", length=64)
*/
private $password;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank(message="Please enter a valid name")
*/
private $fullname;
/**
* #var array
* #ORM\Column(name="roles", type="json_array")
*/
protected $roles;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Problem", mappedBy="createdby")
*/
protected $problems;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Feed", mappedBy="createdby")
*/
protected $feeds;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Comment", mappedBy="createdby")
*/
protected $comments;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Submission", mappedBy="user_id")
*/
protected $submissions;
// other properties and methods
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getSalt()
{
// The bcrypt algorithm doesn't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
// other methods, including security methods like getRoles()
/**
* #return array
*/
public function getRoles()
{
return $this->roles;
}
public function setRoles(array $roles){
$this->roles = $roles;
return $this;
}
/**
* #return mixed
*/
public function getFullname()
{
return $this->fullname;
}
/**
* #param mixed $fullname
*/
public function setFullname($fullname)
{
$this->fullname = $fullname;
}
/**
* #return mixed
*/
public function getUsrname()
{
return $this->usrname;
}
/**
* #param mixed $usrname
*/
public function setUsrname($usrname)
{
$this->usrname = $usrname;
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getSubmissions()
{
return $this->submissions;
}
Problem.php
/**
* Class Problem
* #package AppBundle\Entity
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProblemRepository")
* #ORM\Table(name="problem")
* #ORM\HasLifecycleCallbacks()
*
*/
class Problem
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=True)
* #Assert\NotBlank(message="Please enter a valid title")
*/
protected $title;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Please enter a valid description")
*/
protected $description;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Please enter a valid value")
*/
protected $points;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Please enter a valid flag")
*/
protected $flag;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Please enter a valid value")
*/
protected $category;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="problems")
* #ORM\JoinColumn(name="createdby", referencedColumnName="id")
*/
protected $createdby;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Submission", mappedBy="problem_id")
*/
protected $submissions;
/**
* #Gedmo\Slug(fields={"title"})
* #ORM\Column(type="string", length=255, unique=false,)
*/
protected $slug;
/**
* #return mixed
*/
public function getTitle()
{
return $this->title;
}
/**
* #param mixed $title
*/
public function setTitle($title)
{
$this->title = $title;
}
/**
* #return mixed
*/
public function getDescription()
{
return $this->description;
}
/**
* #param mixed $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* #return mixed
*/
public function getPoints()
{
return $this->points;
}
/**
* #param mixed $points
*/
public function setPoints($points)
{
$this->points = $points;
}
/**
* #return mixed
*/
public function getFlag()
{
return $this->flag;
}
/**
* #param mixed $flag
*/
public function setFlag($flag)
{
$this->flag = $flag;
}
/**
* #return mixed
*/
public function getCategory()
{
return $this->category;
}
/**
* #param mixed $category
*/
public function setCategory($category)
{
$this->category = $category;
}
/**
* #return mixed
*/
public function getCreatedby()
{
return $this->createdby;
}
/**
* #param mixed $createdby
*/
public function setCreatedby($createdby)
{
$this->createdby = $createdby;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set slug
*
* #param string $slug
*
* #return Problem
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* #return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* #return mixed
*/
public function getSubmissions()
{
return $this->submissions;
}
/**
* #param mixed $submissions
*/
public function setSubmissions($submissions)
{
$this->submissions = $submissions;
}
}
Submission.php
class Submission
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Problem", inversedBy="submissions")
* #ORM\JoinColumn(name="problem_id", referencedColumnName="id")
*/
protected $problem_id;
/**
* #ORM\Column(type="boolean")
*/
protected $correct = false;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User", inversedBy="submissions")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user_id;
/**
* #ORM\Column(type="string")
* #Assert\NotBlank(message="Flag cannot be blank")
*/
protected $submission_flag;
/**
* #return mixed
*/
public function getProblemId()
{
return $this->problem_id;
}
/**
* #param mixed $problem_id
*/
public function setProblemId($problem_id)
{
$this->problem_id = $problem_id;
}
/**
* #return mixed
*/
public function getCorrect()
{
return $this->correct;
}
/**
* #param mixed $correct
*/
public function setCorrect($correct)
{
$this->correct = $correct;
}
/**
* #return mixed
*/
public function getUserId()
{
return $this->user_id;
}
/**
* #param mixed $user_id
*/
public function setUserId($user_id)
{
$this->user_id = $user_id;
}
/**
* #return mixed
*/
public function getSubmissionFlag()
{
return $this->submission_flag;
}
/**
* #param mixed $submission_flag
*/
public function setSubmissionFlag($submission_flag)
{
$this->submission_flag = $submission_flag;
}
}
Any suggestions on accessing each user and their total points would be highly appreciated.
getSubmissions() returns collenction and getProblemId() is method of Submissions.
So as soon as you have many Submissions fo each user - i would create some getTotalPoints() method for User where you can run in foreach.
This kind of way:
public function getTotalPoints()
{
$submissions = $this->getSubmissions();
$points = 0;
foreach ($submissions as $submission) {
$points += $submission->getProblemId()->getPoints();
}
return $points;
}
You could do something like this:
$userService = $this->container->get('userservice');
$users = $userService->findAll();
$pointPerUser = array();
foreach ($users as $user){
$userSubmissions = $user->getSubmissions();
$pointPerUser[$user->getId()] = 0;
foreach ($userSubmissions as $userSubmission) {
// Probably here you want to check if the submission is correct
$pointPerUser[$user->getId()] += $userSubmission->getProblemId()->getPoints();
}
}
By the way, the submission attribute shouldn't be $problem_id but $problem, and the method is getProblem(), as you are getting a Problem instance, not an id. The field name for the table is ok, as you are storing an id.

How do I convert Doctrine Annotation Information To Array?

I have the following inventory entity object for Doctrine that I created for use in Symfony 3.0.
Simply put how do I get access to the information that I put in the entity via annotations?
For example, companyID, has a ManyToOne annotation that references inversedBy="location". This particular information is very useful to me so I can tell if its a child relationship by foreign key or parent relationship.
If I can get the information about the entity that I described via annotations somehow in an array with Doctrine that would be great. Is this possible to do? Essentially I'm looking for introspection functions on the entity.
<?php
namespace AppBundle\Entity;
use Gedmo\Translatable\Translatable;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Inventory
*
* #ORM\Table(name="distribution_inventory")
* #ORM\Entity(repositoryClass="AppBundle\Repository\InventoryRepository")
*/
class Inventory implements Translatable {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Gedmo\Translatable
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #var string
* #Gedmo\Slug(fields={"name","id"},suffix=".html")
* #ORM\Column(name="inventoryslug", type="string", length=255, nullable=false, nullable=true)
*/
private $inventoryslug;
/**
* #var string
*
* #ORM\Column(name="barcode", type="string", length=255, nullable=true)
*/
private $barcode;
/**
* #var string
* #Gedmo\Translatable
* #ORM\Column(name="description", type="text", nullable=true)
*/
private $description;
/**
* #var string
* #ORM\Column(name="imagename", type="string", nullable=true)
*/
private $imagename;
/**
* #Gedmo\Locale
* Used locale to override Translation listener`s locale
* this is not a mapped field of entity metadata, just a simple property
*/
private $locale;
/**
* #var \AppBundle\Entity\InventoryCategory
*
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\InventoryCategory")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="categoryid", referencedColumnName="id")
* })
*/
private $category;
/**
* #var \AppBundle\Entity\Company
* #Assert\Type(type="\AppBundle\Entity\Company")
* #Assert\Valid()
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Company", inversedBy="Location")
* #ORM\JoinColumn(name="companyid", referencedColumnName="id", onDelete="CASCADE")
*/
protected $companyId;
/**
* #var \DateTime $created
*
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $created;
/**
* #var \DateTime $updated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime")
*/
private $updated;
/**
* #var string
* #ORM\Column(name="defaultsellprice",precision=14, scale=2, nullable=true)
*/
private $defaultsellprice;
/**
* #var boolean
*
* #ORM\Column(name="onwaycount", type="integer", nullable=false)
*/
private $onwaycount;
/**
* #var boolean
*
* #ORM\Column(name="instorecount", type="integer", nullable=false)
*/
private $instorecount;
/**
* #var boolean
*
* #ORM\Column(name="wayoutcount", type="integer", nullable=false)
*/
private $wayoutcount;
/**
* #var boolean
*
* #ORM\Column(name="instore", type="string", length=10, nullable=false)
*/
private $instore;
/**
* #var string
*
* #ORM\Column(name="isarchived", type="string", length=5, nullable=false,options={"default":false})
*/
private $isarchived;
/**
* #var string
*
* #ORM\Column(name="archivestatus", type="string", length=5, nullable=false,options={"default":true})
*/
private $archivestatus;
function __construct() {
$this->onwaycount=0;
$this->instore=FALSE;
$this->instorecount=0;
$this->wayoutcount=0;
}
/**
* Get id
*
* #return int
*/
public function getId() {
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Inventory
*/
public function setName($name) {
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName() {
return $this->name;
}
/**
* Set barcode
*
* #param string $barcode
*
* #return Inventory
*/
public function setBarcode($barcode) {
$this->barcode = $barcode;
return $this;
}
/**
* Get barcode
*
* #return string
*/
public function getBarcode() {
return $this->barcode;
}
/**
* Set description
*
* #param string $description
*
* #return Inventory
*/
public function setDescription($description) {
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription() {
return $this->description;
}
public function getImagename() {
return $this->imagename;
}
public function getCategory() {
return $this->category;
}
public function getCompanyId() {
return $this->companyId;
}
public function getCreated() {
return $this->created;
}
public function getUpdated() {
return $this->updated;
}
public function getOnwaycount() {
return $this->onwaycount;
}
public function getInstorecount() {
return $this->instorecount;
}
public function getWayoutcount() {
return $this->wayoutcount;
}
public function getInstore() {
return $this->instore;
}
public function setImagename($imagename) {
$this->imagename = $imagename;
return $this;
}
public function setCategory(\AppBundle\Entity\InventoryCategory $category) {
$this->category = $category;
return $this;
}
public function setCompanyId(\AppBundle\Entity\Company $companyId) {
$this->companyId = $companyId;
return $this;
}
public function setCreated(\DateTime $created) {
$this->created = $created;
return $this;
}
public function setUpdated(\DateTime $updated) {
$this->updated = $updated;
return $this;
}
public function setOnwaycount($onwaycount) {
$this->onwaycount = $onwaycount;
return $this;
}
public function setInstorecount($instorecount) {
$this->instorecount = $instorecount;
return $this;
}
public function setWayoutcount($wayoutcount) {
$this->wayoutcount = $wayoutcount;
return $this;
}
public function setInstore($instore) {
$this->instore = $instore;
return $this;
}
public function getDefaultsellprice() {
return $this->defaultsellprice;
}
public function setDefaultsellprice($defaultsellprice) {
$this->defaultsellprice = $defaultsellprice;
return $this;
}
public function getInventoryslug() {
return $this->inventoryslug;
}
public function setInventoryslug($inventoryslug) {
$this->inventoryslug = $inventoryslug;
return $this;
}
public function setTranslatableLocale($locale) {
$this->locale = $locale;
}
public function getIsarchived() {
return $this->isarchived;
}
public function getArchivestatus() {
return $this->archivestatus;
}
public function setIsarchived($isarchived) {
$this->isarchived = $isarchived;
return $this;
}
public function setArchivestatus($archivestatus) {
$this->archivestatus = $archivestatus;
return $this;
}
}
Found this not sure if it will help though (http://tocacar.com/2013/01/25/doctrine2-object-introspection/)
To get an array of metadata:
$cmf = $em->getMetadataFactory();
$metadata = $cmf->getMetadataFor(\AppBundle\Entity\Inventory::class);
//Doctrine\ORM\Mapping\ClassMetadata instance
//as array:
$metadata = (array) $metadata;
To get the inversed information:
$metadata->getAssociationMapping('companyId')['inversedBy'];
//as array
$metadata['associationMappings']['companyId']['inversedBy'];
You can find more info on the docs.

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);
}

Symfony2 Entity doctrine2 don't update field

I have this entity:
Profile.php
/**
* LoPati\BlogBundle\Entity\Profile
*
* #ORM\Table(name="profile")
* #ORM\Entity
* #Gedmo\TranslationEntity(class="LoPati\BlogBundle\Entity\ProfileTranslation")
*/
class Profile
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Gedmo\Translatable
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*/
protected $name=null;
/**
* #var text $description
* #Gedmo\Translatable
* #ORM\Column(name="description", type="text", nullable=true)
*/
protected $description=null;
/**
* #ORM\OneToMany(targetEntity="ProfileTranslation", mappedBy="object", cascade={"persist", "remove"})
*/
protected $translations;
/**
* Required for Translatable behaviour
* #Gedmo\Locale
*/
protected $locale;
public function __construct()
{
$this->translations = new ArrayCollection;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function getLocale()
{
return $this->locale;
}
public function setLocale($locale)
{
$this->locale = $locale;
}
public function setName($name)
{
$this->name=$name;
}
public function getName()
{
return $this->name;
}
public function setDescription($description)
{
}
public function getDescription()
{
return $this->description;
}
public function getTranslations()
{
return $this->translations;
}
public function addTranslation(ProfileTranslation $t)
{
$this->translations->add($t);
$t->setObject($this);
$this->name = $this->translations[0];
$this->description = $this->translations[1];
}
public function removeTranslation(ProfileTranslation $t)
{
$this->translations->removeElement($t);
}
public function setTranslations($translations)
{
$this->translations = $translations;
$this->name = $this->translations[0];
$this->description = $this->translations[1];
}
public function __toString()
{
return "hola";
}
}
And ProfileTranslation.php
/**
* #ORM\Entity
* #ORM\Table(name="profile_translations",
* uniqueConstraints={#ORM\UniqueConstraint(name="lookup_unique_idx", columns={
* "locale", "object_id", "field"
* })}
* )
*/
class ProfileTranslation extends AbstractPersonalTranslation
{
/**
* Convinient constructor
*
* #param string $locale
* #param string $field
* #param string $content
*/
public function __construct($locale = null, $field = null, $content = null)
{
$this->setLocale($locale);
$this->setField($field);
$this->setContent($content);
}
/**
* #ORM\ManyToOne(targetEntity="Profile", inversedBy="translations")
* #ORM\JoinColumn(name="object_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $object;
public function __toString()
{
return $this->getContent();
}
}
I want that when edit arraycollection that is a ProfileTranslation table, then also update the name and description field but form Profile Table, and it be the first element that collection.
It work when I create new Profile, but when I edit this profile, only update the ProfileTranslation Table and not Profile table.
How I can do it?
Your implementation turns away a little too classic Translatable use.
You could maybe have a look about TranslationFormBundle and its Demo if it right for you.

Categories