Symfony collection detach instead remove - symfony

is it possible whem element is removed from collection in front end to keep the item marked to remove in DB, only detached it from original object, so the reference to that object will be null?
It's done via Symfony basic collection remove/add way in front end. But no matter what I try, the entity, which user remove from the form in front end is deleted.
Even that I comment the code which actually remove it from collection, entity is still deleted.
/**
* Remove address.
*
* #param Address $address
*/
public function removeAddress(Address $address)
{
//$this->addresses->removeElement($address);
$address->setContact(null);
}

Related

Symfony 3 - Update Many-To-Many

I have been looking around for a clean solution on how to update (keep in sync) a many to many relationship?
I have the following scenario:
A Sprint Entity owns the Many To Many relationship towards the Ticket entity.
When editing a Ticket (or Sprint, but I am not there yet), I want to be able to select (checkboxes) the Sprints that this ticket belongs to.
Upon persistance (save), I want to update my join table tickets_sprint (which is just a join table on ticket_id, sprint_id).
Adding Sprints to the Ticket seems easy enough, but removing Sprints from the Ticket is not reflected at all.
Code
Ticket Entity contains this method for adding a Ticket to a Sprint:
public function setSprints($sprints) {
/**
* #var $sprint \AppBundle\Entity\Sprint
*/
foreach ($sprints as $sprint) {
$this->sprints[] = $sprint;
$sprint->addTicket($this);
}
}
I have read here that the only way to go would be to remove all relations and re-save them upon persistance.
Coming from the Laravel world, this hardly feels like a good idea :)
This is how it is done in Laravel:
/**
* #param \App\User $user
* #param \App\Http\Requests\StoreUserRequest $request
* #return \Illuminate\Http\RedirectResponse
* Update the specified resource in storage.
*/
public function update(User $user, StoreUserRequest $request)
{
$user->fill($request->input());
$user->employee_code = strtolower($user->employee_code);
$user->roles()->sync($request->role ? : []);
$user->save();
\Session::flash('flash_message_success', 'The user was successfully updated.');
return redirect()->route('frontend::users.show', [$user]);
}
All suggestions are welcome!
The EntityType that you may use to create a multiple selectbox doesn't have a by_reference option like CollectionType.
If your Ticket Entity use the "inversedBy" side, you don't need to add the reference in the other object. So you can symply do this :
public function setSprints($sprints) {
$this->sprints = $sprints;
}
Maybe this will be enough to add and remove your elements automatically (Sorry didn't try).
Otherwise you have to do it manually and you can create a new method to remove elements returns by the difference between your new ArrayCollection and the old one.

Symfony2 data fixtures

very new to Symfony2 so trying to working things out. Got a couple of tables. First one is called availability_alert. This table has the usual stuff like an id, but the important thing to note is that it does not have a link to anything.
The second table is call booking_class. This one has a couple of fields, one of which is availability_alert_id. This value is linked to the id field in my first table.
I had a working application, but decided to move it to Symfony. I used my existing database to produce some entity classes. In my BookingClass Entity, I have a link to the AvailabilityAlert id.
/**
* #var \AlertBundle\Entity\AvailabilityAlert
*
* #ORM\ManyToOne(targetEntity="\AlertBundle\Entity\AvailabilityAlert")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="availability_alert_id", referencedColumnName="id")
* })
*/
private $availabilityAlert;
Now for some reason, it set the setter to this field like so
public function setAvailabilityAlert(\AlertBundle\Entity\AvailabilityAlert $availabilityAlert = null)
{
$this->availabilityAlert = $availabilityAlert;
return $this;
}
So I first want to make sure this is correct?
Next, I am doing some Data Fixtures, so I do
$alert = new AvailabilityAlert();
And then set a few other bits and pieces. I then do one for the other table
$bookingClass = new BookingClass();
However, when I try to set the id
$bookingClass->setAvailabilityAlert($this.$alert->getId());
It tells me it is expecting an AvailabilityAlert but instead it is getting a String.
So I was just wondering what I am doing wrong here?
Thanks
The problem is that you're trying to add a string instead of an AvailabilityAlert object to your BookingClass object.
Instead of this:
$bookingClass->setAvailabilityAlert($this.$alert->getId());
Simply try this:
$bookingClass->setAvailabilityAlert($alert);

ResultSetManager clobbered after postLoad event handler

I have a postLoad event listener that executes a query to retrieve some file data that is popped into an array on the entity that is being loaded. I am doing this because we have a number of different items that need to be added to the entity, but aren't essential to the entity. Right now it is files, but we will eventually have at least 7 of these "items". Instead of creating 7 different mappings to the individual "items", we decided to implement them as services in Symfony that drop the "payload" that they provide into the infoArray. Now when we want to add a new "item" we don't have to edit dozens of business object to add a new mapping, we can just add it to the infoArrray (keyed by service name) and whoever needs it can get it from that array.
so my entity looks like
/**
* #var integer $id
*
* #ORM\Column(name="ID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #Type("integer")
* #SerGroups({"modulerevision", "module_revision"})
*/
protected $id;
.
.
.
public $servicesArray;
In the event listener I end up calling the following method
public function getFiles(ConsumerInterface $entity, $fullPath = false){
$query = $this->em->createQuery(
'SELECT f
FROM FileManagerBundle:File f
JOIN f.owners o
WHERE o.id = ?1');
$query->setParameter(1, $entity->getOwner());
$files = $query->getResult();
return $files;
}
This works great. The query is executed and I get my array of files and I push it onto $infoArray in my entity.
After running the postLoad event code, we jump back in to Doctrine\ORM\Internal\Hydration\ObjectHydrator at line 480
if ($this->_rsm->isMixed) {
At this point the private varialble _rsm is no longer set and the house comes crashing down with the following exception
Notice: Trying to get property of non-object in /var/www/symfony/xesapps/vendor/doctrine/orm/lib/Doctrine/ORM/Internal/Hydration/ObjectHydrator.php line 480
Is it possible that calling the additional query during the postLoad event is killing the ResultSetManager being used to hydrate the object being loaded? If so, is there any way around this? Do I need to approach this in a different way?
PS - this works great in another controller where the only object being hydrated is the parent object. It fails in a controller where the object is being hydrated as part of an object graph.
You are probably trying to access an association in the postLoad event.
From the documentation:
Note that the postLoad event occurs for an entity before any
associations have been initialized. Therefore it is not safe to access
associations in a postLoad callback or event handler.
Just a note for anyone who finds this question in the future. There is a workaround. It isn't pretty, but it seems to work. You can read more at http://www.doctrine-project.org/jira/browse/DDC-1010
That ticket also points to committed code in doctrine two work around this issue - https://github.com/doctrine/doctrine2/commit/8d13601e39d0fdefdd1d2c0a85704c440b8bdd37, so hopefully a better solution will be coming soon.

Symfony/Doctrine 2 - Use config parameter in Entity

I have a tree of Employee objects (they are in a tree-like hierarchy, with everyone having one leader, and all leaders having more employees). All the Employees have a integer parameter called units.
/**
* #ORM\Entity
* #ORM\Table(name="employees")
*/
class Employee
{
/**
* #ORM\Id
* #ORM\Column(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Employee", mappedBy="leader")
*/
protected $employees;
/**
* #ORM\ManyToOne(targetEntity("Employee", inversedBy="employees")
*/
protected $leader;
}
I need to get all the employees, who have at most N units, where N is defined in config.yml. At first, I was trying to push $configContainer into $GLOBALS, and use it in ArrayCollection::filter()'s Closure. Now I found a method, so I can use variables in the Closure:
public function getBestEmployees(&$configContainer)
{
return $this->getAllEmployees()->filter(
function bestEmployees($employee) use ($configContainer)
{
return ($employee->getUnits() >= $configContainer->getParameter('best_unit_count'));
}
);
}
Now I wonder if there is any other way to access the configuration parameters from an Entity, or do I really have to pass the whole configContainer as a reference? Or am I doing it totally wrong?
You shouldn't be accessing the service container at all inside entities. The value itself should be passed instead
public function getBestEmployees($bestUnitCount)
{
return $this->getAllEmployees()->filter(function ($employee) use ($bestUnitCount) {
return $employee->getUnits()->count() >= $bestUnitCount;
});
}
Of course, we haven't actually solved the problem yet: the parameter still needs to be fetched from the container somewhere. If this method gets invoked mostly in controller actions, I wouldn't bother doing any extra work to make things cleaner and would pass the container parameter straight in the controller action.
However, should there be a need to get the best employees in a Twig template, for example, it would be nice if it wouldn't be necessary to pass the parameter. One possibility would be using a setter method and passing the parameter down beforehand to each and every entity that gets retrieved from the database. You could do this either in repositories or entitiy managers. The most advanced solution would be to listen to the postLoad event and pass the parameter in an event listener.

Symfony 2 - flush in postUpdate fire preUpdate event

I detected this problem "thanks" to an exception I got:
Catchable Fatal Error: Argument 3 passed to
Doctrine\ORM\Event\PreUpdateEventArgs::__construct()
must be an array, null given, called in
/.../vendor/doctrine/lib/Doctrine/ORM/UnitOfWork.php on line 804
and defined in
/.../vendor/doctrine/lib/Doctrine/ORM/Event/PreUpdateEventArgs.php line 28
I am working on a project that requieres a specific logic:
When the order field in entity book is modified, I need to update field books_order_modified_at in the parent entity bookstore (this field allows me to know whether the order of books in a bookstore was changed).
I decided to do it in an event listener since there are many places in the code that might change the order of books.
I didn't find any way to update a related entity from preUpdate event, so I have a private field in the listener class which I use to tell the postUpdate event to update the relevant bookstore entity.
My problem is that when I do so the preUpdate event of the book entity is fired.
When I check the change-set it contains only the modified_at field, but it has the same value before and after.
If someone has another idea how to solve the problem - great.
If not - any idea how can I prevent the preUpdate event from being fired when the flush is called in teh postUpdate event??
Actually, this is a problem from doctrine Doctrine Issue DDC-2726. Solved it by adding a clear call on the entity manager after the flush in the listener so the 3-rd argument to that constructor, which is actually the entityChangeSets, will be re-written.
What about updating the modified_at within your entities and let doctrine handle it? You would change your setOrder method in your book to update the BookOrder entity like this:
class Book {
public function setOrder($order) {
// modify book
$this->bookOrder->updateModifiedAt();
}
}
Of course your BookOrder would have to implement modifiedAt:
class BookOrder {
public function updateModifiedAt() {
$this->modifiedAt = new \DateTime();
}
}
If you use other classes for your Datetime, you of course have to change this code!
Doctrine should recognize that BookOrder has changed and should update it without any need to use a event listener.
I can suggest you to use Timestampable extension for Doctrine from DoctrineExtensionsBundle.
By using it you don't need to set created_at or modified_at values. This extension does it automatically. Even it can set modified_at only when specific fields were modified. See example.
I think you are writing something like this extension. So, you don't need to do that because this is already done :)
I had a similar problem to this. Trying to use preupdate to modify child elements caused the same error. In the end, my solution to simply update the children belonging to the parent. No explicit call to flush required.
/**
* Update expiry dates for all runners belonging to a campaign
*
* #param $runners
* #param $expiryDate
*/
private function updateCampaignRunners($runners, $expiryDate){
foreach($runners as $runner){
$runner->setExpiresAt($expiryDate);
$this->getModelManager()->update($runner);
}
}
/**
* Post update and persist lifecycle callback
*
* #param Campaign $campaign
*/
private function postAction(Campaign $campaign)
{
$runnerExpire = $this->getForm()->get("runnerExpire")->getData();
if($runnerExpiryDate && $campaign->getRunners()){
$this->updateCampaignRunners($campaign->getRunners(), $runnersExpiryDate);
}
}

Resources