Other way to update doctrine's OneToMany relationship in symfony framework - php

I using symfony 3.4 and doctrineORM 2.5, I have an entity called Project which contains a OneToMany relationship to other entity called Opportunity
So, in the form edit UI of Project entity I have a multiselect choices of opportunities, the user can select or deselect any opportunity in this list.
When I submit the form to ProjectController--->editAction doctrine update automatically opportunities's ArrayCollection with the new selected values, that's good :), but after persist Project object doctrine does not remove that user is deselected from the multi selection list.
class Project{
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="Module\CRMBundle\Entity\Opportunity", mappedBy="project")
*/
private $opportunities;
}
class Opportunity{
/**
* #var Project
* #ORM\ManyToOne(targetEntity="Module\CRMBundle\Entity\Project", inversedBy="opportunities")
* #ORM\JoinColumn(name="project_code", referencedColumnName="code")
*/
private $project;
}
My solution is to reset the relationship everytime somebody edits Project like this :
public function editAction(Request $request, Project $project){
$form = $this->createForm('Module\CRMBundle\Form\ProjectType', $project);
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
//Get all old opportunities of this project and set their project to null
$oldOpportunities = $em->getRepository('CRMBundle:Opportunity')->findBy(array(
'project' => $project
));
foreach ($oldOpportunities as $opportunity){
$opportunity->setProject(null);
$em->persist($opportunity);
}
//If request is comming we set this project to whole opportunities
if ($request->isMethod('POST')) {
foreach ($project->getOpportunities() as $opportunity){
$opportunity->setProject($project);
}
}
$em->persist($project);
$em->flush();
}
This solution work very well, but there is a best way to update this relationship because sometimes I have a very lot Opportunities from the owning side (Project), I think that very expensive query that doctrine will excute

Related

Doctrine ManyToOne relationship - auto-remove on "set"

I'm working with relationships in Doctrine (using Symfony 5).
What I have is these 2 relations:
User
Availability
User has an ID and has Many Availabilities.
So Entity User has
/**
* #ORM\OneToMany(targetEntity="UserAvailability", mappedBy="user")
*/
private $availability;
and the reverse on Entity Availability.
Availability is a relation with:
id, user_id, day_name, start_time and end_time, that simple.
What I already achieved with ManyToMany and I want to achieve in this case too is:
I need to receive the entire set of availabilities for a User from the client and use it to update the availabilities of my User, so I defined a setAvailability method which receives a Collection of Availability entities and simply does
$this->availabilities = $availabilities.
This works when I add new availabilities but the ones that are on the DB and not in the collection are not dropped when I persist the entity.
The same method works flawlessly with ManyToMany Relationship.
What am I missing?
*** UPDATE **
public function setAvailability($availability): self
{
$this->availability = $availability;
return $this;
}
this same code works when removing relations in ManyToMany relationship but not in ManyToOne, the attribute "availability" is correctly set, but when using persist/flush the availability which was removed is not removed on the DB.
Thanks
Try to set the attributes as in the example from the doctrine documentation below :
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** #Entity */
class User
{
// ...
/**
* One user has many availabilities. This is the inverse side.
* #OneToMany(targetEntity="Availability", mappedBy="user")
*/
private $availabilities;
// ...
public function __construct() {
$this->availabilities = new ArrayCollection();
}
}
/** #Entity */
class Availability
{
// ...
/**
* Many availabilities have one user. This is the owning side.
* #ManyToOne(targetEntity="User", inversedBy="availabilities")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
// ...
}
the attribute mappedBy and inversedBy are necessary for relations

laravel load scope after model created

I have this type of scope inside RoadOffer model:
public function scopeOfOfferRelationships($query){
return $query->with(["priceNegotiations", "driver.user", "roadTransport", "serviceProvider", "serviceProvider.company"]);
}
then somewhere inside my code I have code like that:
$offer = Offer::where('id', $request->offer_id)->first();
$offer->status = 1;
$offer->save();
return $offer->query()->OfOfferRelationships()->where('id', $request->offer_id)->first();
I have to write $offer->query()->OfOfferRelationships()->where('id', $request->offer_id)->first(); in order to load relationships from scope. is there any better way than using query()?
If I used load() function I could load these relationships but I want to have this scopeOfOfferRelationships is this the only way?
Load relations whenever new record created
I think observers best approach to achieve this.
First create a an observer Offer model.
php artisan make:observer OfferObserver
Then register OfferObserver in AppServiceProvider
Offer::observe(OfferObserver::class)
In the created() method load offer relations. This method will fire when you created new record. Also if you want to load relations after updating model, copy and paste below code into the updated() method
/**
* Handle the offer "created" event.
*
* #param Offer $offer
* #return void
*/
public function created(Offer $offer)
{
$offer->load([
"priceNegotiations",
"driver.user",
"roadTransport",
"serviceProvider",
"serviceProvider.company"
]);
}
load relations only when you need
in Offer Model create method for loading relations.
public function loadRelations()
{
$this->load([
"priceNegotiations",
"driver.user",
"roadTransport",
"serviceProvider",
"serviceProvider.company"
]);
}
Then load relations with loadRelations() method when ever you need
// update process
$offer->save();
$offer->loadRelations()
return $offer;

#ORM\OneToMany Is retrieving the virtual deleted entries

I have a relation in Doctrine2 #ORM\OneToMany, suposing that i have table school and student, in the entity school i have the #ORM\OneToMany column students,
and i also have a virtual deletion column deleted_at, so every student that has the deleted_at different of null is a deleted student that is supposed not to appear in the column #ORM\OneToMany $students. How can i make this filter?
/**
* #var \Doctrine\Common\Collections\ArrayCollection
*
* #ORM\OneToMany(targetEntity="App\Oceano\Entities\Student",
* mappedBy="cartCore",
* cascade={"all"}
* )
*/
private $students;
So, when i call for school students, it is retrieving also the deleted ones.
$schoolObj->getStudents();
Any Solution using annotation or some clean change?
You practically described Laravel's soft deleting feature. So, if you use it, you do not need to do anything and soft deleted students will not appear. You just need to add Illuminate\Database\Eloquent\SoftDeletes trait to the Student model.
If you're using some own functionality, create a local scope in the Student model:
public function scopeNotDeleted($query)
{
return $query->whereNull('deleted_at');
}
And use it:
Student::notDeleted()->get();
Or:
$school->students()->notDeleted()->get();
You can use Criteria filter class in your entity to students collection which are not deleted
protected getStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->eq('deleted_at', null));
return $this->students->matching($criteria);
}
To get deleted students you could write it like
protected getDeletedStudents() {
$criteria = \Doctrine\Common\Collections\Criteria::create()
->where(\Doctrine\Common\Collections\Criteria::expr()->neq('deleted_at', null));
return $this->students->matching($criteria);
}
How filter data inside entity object in Symfony 2 and Doctrine

How to automatically remove nested objects in Symfony?

I'm a big begginer in Symfony, coming from Ruby on Rails world and trying to get same behaviours.
I have a simple application with 2 entities : Product and Category, with the relation Product belongs to Category and Category has many Products.
class Category {
[ ... ]
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", cascade={"all"})
*/
protected $products;
}
class Product {
[ ... ]
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products", cascade={"all"})
*/
protected $category;
}
What I'm trying to do is to delete every nested products when I'm deleting a Category.
My current action looks like
public function deleteAction($id, Request $request)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:Category');
$category = $repository->find($id);
$em = $this->getDoctrine()->getManager();
$em->remove($category);
$em->flush();
return $this->redirect('/categories/');
}
A simple method could be remove all the products in the controller, but it's not very maintainable and not very object oriented. I'm looking about a practice to remove all the products of the deleted category directly in the model. A method, in RoR, is the callbacks (named after_destroy), automatically called when the object is destroyed. Is there any looking-like method in Symfony ?

Symfony2 creating and persisting entity relationships

I have two entities Skin and Email. I want Email to be a part of the Skin entity, however I can't use the console to update schema automatically right now, and that is probably why I can't get the relationship to work.
So I want to store all the Emails in the Email entity, and I want to assign an email to a desired Skin. So the relationship should me OneToOne.
I am not an expert in MySQL relationships so this is what I made by myself:
In my main Skin class I created a field in which I want to store the emails id:
/**
*
* #ORM\OneToOne(targetEntity="MediaparkLt\UserBundle\Entity\Email", inversedBy="skin")
* #ORM\JoinColumn(name="email_id", referencedColumnName="id")
*/
protected $email_id;
In the Email class I created the skin field in which I want to show the skins Id:
/**
*
* #ORM\OneToOne(targetEntity="MediaparkLt\SkinBundle\Entity\Skin", mappedBy="email_id")
* #ORM\JoinColumn(name="skin", referencedColumnName="id")
*/
protected $skin;
Now in my CMS I create the new email like this:
public function saveAction(Request $request) {
$item = new Email();
$type = new EmailType($this->container->getParameter("langs"));
$form = $this->createForm($type, $item);
$form->handleRequest($request);
$em = $this->getEntityManager();
if ($form->isValid()) {
$this->upload($form, $item);
$em->persist($item);
$em->flush();
return $this->redirect($this->generateUrl('cms_skin_email_list', array('skin_id' => $item->getId())));
}
return array('form' => $form->createView(), 'item' => $item);
}
And this is the form:
public function buildForm(FormBuilderInterface $builder, array $option) {
$builder->add('title', 'text', array('label' => 'cms.Title'));
$builder->add('registration_content', 'textarea', array('label' => 'cms.registration.content'));
}
Now in mysql I create the relationship like this:
ALTER TABLE `skin` ADD CONSTRAINT `emailId` FOREIGN KEY (`email_id`) REFERENCES `lottery`.`email`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;
Now when I create a new email I still get NULL on both entitites.
When you create two entities with a one-to-one relationship, both entities need to be persisted either explicitly or by using cascade persist on one side of the relationship. You also need to explicitly set both sides of the relationship.
Doctrine - Working with Associations - Transitive persistence / Cascade Operations
States:
Even if you persist a new User that contains our new Comment this code
would fail if you removed the call to
EntityManager#persist($myFirstComment). Doctrine 2 does not cascade
the persist operation to all nested entities that are new as well.
Doctrine - Working with Associations - Establishing Associations
States:
In the case of bi-directional associations you have to update the
fields on both sides
With no cascade persist you need something like this:
$skin = new Skin();
$email = new Email();
$skin->setEmail($email);
$email->setSkin($skin);
$em->persist($email);
$em->persist($skin);
$em->flush();
With cascade persist on the Skin side of the relationship you can omit $em->persist($skin). Note if you cascade persist you would usually also cascade remove:
* #ORM\OneToOne(targetEntity="MediaparkLt\UserBundle\Entity\Email", inversedBy="skin", cascade={"persist", "remove"})

Categories