Creating new entities and changing an entity in prePersist - doctrine-orm

in a sense I would like to transfer my old question Flushing in postPersist possible or not? to the prePersistevent.
Suppose the following example
class Entity {
/** #PrePersist */
public function doStuffOnPrePersist(LifecycleEventArgs $args)
{
$this->value = 'changed from prePersist callback!';
$other = new OtherEntity();
$args->getEntityManager()->persist($other);
}
}
called like
$entity = new Entity();
$entity->value = "unchanged";
$em->persist($entity);
$em->flush();
I have 2 fundamental questions:
- Will the value written to db be "unchanged" or "Changed from..."?
- Will the OtherEntity be written to DB?
Assuming both questions are answered with yes, let me extend my question:
Both, Entity and OtherEntity have an Autogenerated id field and OtherEntity has a reference (ManyToOne) to Entity:
class Entity {
/*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
private $id;
/** #PrePersist */
public function doStuffOnPrePersist(LifecycleEventArgs $args)
{
$this->value = 'changed from prePersist callback!';
$other = new OtherEntity();
$other->father = $this; // <-------------
$args->getEntityManager()->persist($other);
}
}
Will this work, too?

Related

SingleValuedAssociationField expected - Doctrine query builder

I want to return my holders of a card with query builder and OneToMany relationship.
I have a problem with Semantical Error:
Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
return $this->getCardRepository()
->createQueryBuilder('c')
->leftJoin('Entity\CardHolder', 'ch', 'c.id = ch.id')
->where('ch.cards = :cards')
->setParameter('cardHolder', '%'.$card.'%')
->orderBy('ch.id', 'desc')
->getQuery()
->getResult();
And in my entities:
class Card
{
/**
* #ManyToOne(targetEntity="LaunchPad\Base\Entity\Card\CardHolder",)
* #JoinColumn(name="holder_id", referencedColumnName="id", nullable=true)
*/
private $cardHolder;
}
And second entity:
class CardHolder
{
public function __construct()
{
$this->cards = new ArrayCollection();
}
/**
* #var
*
* #OneToMany(targetEntity="Entity\Card", mappedBy="cardHolder")
*/
protected $cards;
}

Embedding Relations In Results

I'm using API-Platform to deliver content via API. I have a Potential relationship between users and participants (not all users will have participants but all participants will have at least one user). My main goal is to embed the relationship's User data in the result set of Participants as that result will be consumed by a data table and it would be more efficient to have that data already present within the result opposed to performing an additional request for the data.
e.g.:
{
"#context": "/api/contexts/Participants",
"#id": "/api/participants",
"#type": "hydra:Collection",
"hydra:member": [
{
"#id": "/api/participants/1",
"#type": "Participants",
"id": 1,
"name": "Jeffrey Jones",
"users": [
{
"#id": "/api/users/1",
"#type": "User",
"name": "Jenny Jones"
},
{
"#id": "/api/users/2",
"#type": "User",
"name": "Jessie Jones"
}
]
}
],
"hydra:totalItems": 1
}
However, I'm not sure if this is possible. I have looked at https://api-platform.com/docs/core/serialization#embedding-relations but I am not certain it would work for multiple result sets as the example is one book to one author. However, my scenario is one participant to multiple users.
Also (and I may need to go about this in a more direct manner), I'm using a joining table so that I can assign additional metadata to the relationship. So... participants > joint table (containing additional data) > users (and vice versa). Again, I may need to consider having a direct relationship between participants and users and then using a ParticipantUserMeta table to hold the additional metadata. However, at the moment, I'm leaning towards the join table containing the association as well as the additional metadata.
Here are the basics of my entities (most unnecessary data omitted):
User:
/**
* #ApiResource
* ...
*/
class User implements UserInterface, \Serializable
{
/**
* #var int
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string
* #ORM\Column(type="string")
* #Assert\NotBlank()
*/
private $name = '';
/**
* #ORM\OneToMany(targetEntity="App\Entity\ParticipantRel", mappedBy="user")
*/
private $participants;
public function __construct()
{
$this->participants = new ArrayCollection();
}
public function getId(): int
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
/**
* #return Collection|ParticipantRel[]
*/
public function getParticipants(): Collection
{
return $this->participants;
}
}
ParticipantRel:
/**
* #ApiResource
* ...
*/
class ParticipantRel
{
/**
* #var int The Participant Id
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var int
*
* #ORM\Column(type="boolean")
*/
private $primary_contact;
/**
* #var string Relationship notes
*
* #ORM\Column(type="text", nullable=true)
*/
private $notes;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Participants", inversedBy="users")
* #ORM\JoinColumn(nullable=false)
*/
private $participant;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="participants")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getId (): int
{
return $this->id;
}
public function getPrimaryContact(): ?bool
{
return $this->primary_contact;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function getParticipant(): ?Participants
{
return $this->participant;
}
public function getUser(): ?User
{
return $this->user;
}
}
Participants
/**
* #ApiResource
* ...
*/
class Participants
{
/**
* #var int The Participant Id
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string Participant's first name
*
* #ORM\Column(name="name")
* #Assert\NotBlank
*/
public $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\ParticipantRel", mappedBy="participant")
*/
private $users;
public function __construct() {
$this->users = new ArrayCollection();
}
public function getId (): int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
/**
* #return Collection|ParticipantRel[]
*/
public function getUsers(): Collection
{
return $this->users;
}
}
My question: Is what I'm attempting possible within an entity and if so, what am I missing? I have researched this a lot before coming here but haven't come up w/ any solution as most of the solutions I see involve a Twig tpl but I'm simply sending the data via api-platform. Any positive direction would be greatly appreciated.
So, it turns out that I just needed to experiment more w/ the Groups option (https://api-platform.com/docs/core/serialization#embedding-relations). Associating all related fields w/ the relative groups on all relative enities did end up returning the results in the desired format.

Doctrine 2 ZF3 self referencing entity is not updating all fields of parent

I have a self referencing entity with parent and children. The strange thing is, when I add the form elements (DoctrineModule ObjectSelect) for parent and children to the form, some other fields of the parent entity doesn't update, when I persist the entity. The child entities are updating fine.
In the update query of the parent aren't these fields which I want to update. It's like doctrine doesn't recognize the changes for the parent (owning side) anymore. Before the persist I get the right entity with the actual changes from the form, but than doctrine doesn't update the changed fields in the query.
When I delete the form elements for parent and children in the form, everything works fine and the parent entity update/persist all fields.
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(type="text")
*/
private $teaser;
/**
* #var string
*
* #Gedmo\Translatable
* #ORM\Column(type="text")
*/
private $description;
/**
* One Category has Many Categories.
* #var Collection
* #ORM\OneToMany(targetEntity="Rental\Entity\Rental", mappedBy="parent")
*/
private $children;
/**
* Many Categories have One Category.
* #ORM\ManyToOne(targetEntity="Rental\Entity\Rental", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="houses_id", nullable=true, onDelete="SET NULL")
*/
private $parent;
public function __construct()
{
$this->children = new ArrayCollection();
}
/**
* Get teaser
*
* #return string
*/
public function getTeaser()
{
return $this->teaser;
}
/**
* Set teaser
*
* #param string $teaser
*/
public function setTeaser($teaser)
{
$this->teaser = $teaser;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set description
*
* #param string $description
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Add $child
* #param Collection $children
*/
public function addChildren(Collection $children)
{
foreach ($children as $child) {
$this->addChild($child);
}
}
/**
* #param Rental $child
* #return void
*/
public function addChild(Rental $child)
{
if ($this->children->contains($child)) {
return;
}
$child->setParent($this);
$this->children[] = $child;
}
/**
* Remove children
* #param Rental $children
*/
public function removeChildren(Collection $children)
{
foreach ($children as $child) {
$this->removeChild($child);
}
}
/**
* Remove child.
*
* #param \Rental\Entity\Rental $child
*
* #return boolean TRUE if this collection contained the specified element, FALSE otherwise.
*/
public function removeChild(Rental $child)
{
return $this->children->removeElement($child);
}
/**
* Get children.
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getChildren()
{
return $this->children;
}
/**
* Set parent.
*
* #param \Rental\Entity\Rental|null $parent
*/
public function setParent(Rental $parent = null)
{
$this->parent = $parent;
}
/**
* Get parent.
*
* #return \Rental\Entity\Rental|null
*/
public function getParent()
{
return $this->parent;
}
Answer based (and might still change) based on discussion in comments with OP under question.
Simple use case: self-referencing Doctrine Entity does not correctly update parent/child related Entity object properties on on-save action.
OP provided Entity.
I'm assuming you have a Form setup for this Entity. I'm also assuming it's correctly setup, as you did not provide it after being asked (because, rightly so, it would mean a lot of code). However, assuming stuff with code makes for a lot of mistakes, which is why I mention it here.
As such, I'm assuming the handling of your Form in the Controller might be faulty. To check, please use the following simplified addAction function and give it a shot (Factory code below).
/**
* #var RentalForm
*/
protected $form;
/**
* #var ObjectManager
*/
protected $objectManager;
public function __construct(ObjectManager $objectManager, RentalForm $form)
{
$this->form = $form;
$this->objectManager = $objectManager;
}
public function addAction()
{
/** #var RentalForm $form */
$form = $this->getForm();
/** #var Request $request */
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
$entity = $form->getObject();
$this->getObjectManager()->persist($entity);
try {
$this->getObjectManager()->flush();
} catch (\Exception $e) {
$message = sprintf(
'Was unable to save the data. Saving threw an error. <br />Code: %s. <br />Message: %s',
$e->getCode(),
$e->getMessage()
);
$this->flashMessenger()->addErrorMessage($message);
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
$this->flashMessenger()->addSuccessMessage(
$this->getTranslator()->translate('Successfully created object.')
);
// TODO replace vars with your own: return $this->redirect()->route($route, $routeParams);
}
$this->flashMessenger()->addWarningMessage(
'Your form contains errors. Please correct them and try again.'
);
}
return [
'form' => $form,
'validationMessages' => $form->getMessages() ?: '',
];
}
Factory for class with the above, modify as needed for you situation
class RentalControllerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
/** #var ObjectManager $objectManager */
$objectManager = $container->get(EntityManager::class);
/** #var FormElementManagerV3Polyfill $formElementManager */
$formElementManager = $container->get('FormElementManager');
/** #var RentalForm $form */
$form = $formElementManager->get(RentalForm::class);
return new RentalController($objectManager, $form);
}
}

Doctrine2 - How to remove ManyToMany associations on add of new ones

I've took a quick look but can't find the answer.
Take the following entities, users who can have many roles:
/**
* #Entity
**/
class User
{
/**
* #ManyToMany(targetEntity="Role", inversedBy="users")
* #JoinTable(name="user_to_roles")
**/
protected $roles;
public function __construct()
{
$this->roles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addRole(Role $role)
{
$role->addUser($this);
$this->roles[] = $role;
}
public function getRoles()
{
return $this->roles;
}
}
and the roles:
/**
* #Entity
**/
class Role
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
**/
protected $id;
/**
* #ManyToMany(targetEntity="User", mappedBy="roles")
**/
protected $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addUser(User $user)
{
$this->users[] = $user;
}
public function getUsers()
{
return $this->users;
}
}
When I want to update a user I will get the reference, and update like so:
$user = $this>entityManager->getReference('App\Entity\User', $id);
$user->addRole($role);
$this->entityManager->persist($user);
$this->entityManager->flush();
However, this will continuously add new roles to my relational table, whereas I just want to update the current row, or delete it and create a new one (whichever is easier). How can I achieve this?
Try to add this function to your entity:
function setRoles($roles) {
$this->roles = new ArrayCollection($roles);
}
And when you update try this:
$user = $this>entityManager->getReference('App\Entity\User', $id);
$roles = array();
// add your roles to the array roles
$user->setRoles($roles);
$this->entityManager->persist($user);
$this->entityManager->flush();

Eager fetching with Sonata list view

I'm using SonataAdmin and SonataDoctrineORMAdmin bundles to manage entities.
The problem is I can't figure out how to eager fetch the related entities in the list view and as the number of listed entities increase the number of queries executed increasing rapidly as well.
I tried adding `fetch="EAGER" to the relation annotations but the profiles show that Sonata executes the separate queries anyway.
Here's one relation worth of code:
Post
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255)
**/
private $name;
/**
* #ORM\ManyToMany(targetEntity="Acme\AppBundle\Entity\Tag", fetch="EAGER")
* #ORM\JoinTable(name="join_post_to_tag",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
* )
**/
private $tags;
public function getId()
{
return $this->id;
}
public function setName($names)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
public function __toString()
{
return $this->getName();
}
}
Tag
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Tag
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="value", type="string", length=255)
*/
private $value;
public function getId()
{
return $this->id;
}
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function getValue()
{
return $this->value;
}
public function __toString()
{
return ($this->getValue()) ? : '';
}
}
The first related query that is run is fetching all the posts:
SELECT DISTINCT p0_.id AS id0, p0_.id AS id1
FROM Post p0_
LEFT JOIN join_post_to_tag j1_ ON p0_.id = j1_.post_id
LEFT JOIN Tag p1_ ON p1_.id = j1_.target_id
ORDER BY p0_.id ASC
But this does not fetch the related tags or even if it does, it still queries it again:
SELECT t0.id AS id1, t0.value AS value2
FROM Tag t0
INNER JOIN join_post_to_tag ON t0.id = join_post_to_tag.tag_id
WHERE join_post_to_tag.post_id = ?
I tried to mess with the createQuery method in the admin class but could not really find a way to make the related entities fetched correctly.
Is there a way to force the list view to eager fetch the required related entities?
You are on the right track, using the createQuery($context) method.
I have achieved eager loading as following:
public function createQuery($context = 'list')
{
$query = parent::createQuery($context); // let sonata build it's default query for the entity
$rootEntityAlias = $query->getRootAlias(); // get the alias defined by sonata for the root entity
$query->join($rootEntityAlias.'.relationFieldName', 'relationFieldAlias'); // manualy define the join you need
$query->addSelect('relationFieldAlias'); // this is the key line. It is not enough to join a table. You have to also add it to the select list of the query, so that it's actualy fetched
// $query->join(...) // repeat the process of joining and selecting for each relation field you need
// $query->addSelect(...)
return $query; // return the altered query to sonata. this will only work for the "list" action.
}
If you're having trouble using this, let me know:)
Further reads on this topic:
SO question
docs