I am trying to remove galleryhasmedia from gallery.
However gallery entity doesn't have removegalleryhasmedia or something.
so I did a clumsy way, but it doesnt work.
$em = $this->getDoctrine()->getManager();
$firstGhmArray = $gallery->getGalleryHasMedias();
echo count($gallery->getGalleryHasMedias()) // before count
$afterGhmArray = array();
foreach ($firstGhmArray as $ghm){
if ($ghm->getId() == $id){ // id is the target id to delete
//delete
}
else {
array_push($afterGhmArray , $ghm);
}
$gallery->setGalleryHasMedias($afterGhmArray);
}
echo count($gallery->getGalleryHasMedias()) // after count
$em->persist($gallery);
$em->flush();
I think if galleryHasMedias are normal array collection.
I can delete the element with this procedure.
I need to do something more for galleryhasmedia??
You can override the Gallery entity and add this function to it :
public function clearGalleryHasMedias()
{
$this->galleryHasMedias->clear();
}
galleryHasMedias field is an ArrayCollection which can be cleared using the clear method. Its weird tho that setting an empty array doesn't clear work but i guess my solution is worth the shot.
Related
I am doing insert multiple rows in symfony. My code inserts very slowly. Does anyone have any ideas that won't help me! Thank you !
$manager = $this->getContainer()->get('doctrine')->getEntityManager();
// data : object
foreach ($data as $value) {
$entitty = $stkModel->getRepository()->findOneBy(['phone' => $value->getPhone()]);
if ($entity) {
$stk = $stkModel->getEntity();
$stk->setName('mina');
$stk->setStatus(1);
$manager->persist($stk);
$manager->flush();
}
if ($stk) {
$entitty = $titleModel->getRepository()->findOneBy(['stkId' => $stk->getId()]);
$title = $titleModel->getEntity();
$title->setName('dev');
$title->setOrder(2);
$manager->persist($title);
$manager->flush();
}
// Here I take $title->getId();
}
First, you need to call persist only at entities that are created with new. But this will not give you speed performance.
The flush slows the application down every time you use it, because here the query will be send to the database. To speed up your application you have to minimize the use of flush. In your case, just do one flush after you updated your entites.
$manager = $this->getContainer()->get('doctrine')->getEntityManager();
// data : object
foreach ($data as $value) {
$entitty = $stkModel->getRepository()->findOneBy(['phone' => $value->getPhone()]);
if ($entity) {
$stk = $stkModel->getEntity();
$stk->setName('mina');
$stk->setStatus(1);
// $manager->persist($stk); <-- Don't need persist here because your object is coming out from the entity manager
}
if ($stk) {
$entitty = $titleModel->getRepository()->findOneBy(['stkId' => $stk->getId()]);
$title = $titleModel->getEntity();
$title->setName('dev');
$title->setOrder(2);
// $manager->persist($stk); <-- same as mention above
}
}
// call flush once at the end
$manager->flush();
In some cases, you need to call flush in every loop in a foreach. That might happen if you need to query it. But in your case you just search for a id, which didn't change anyway.
I've been struggling with an annoying issue for a while. I'm trying to create Notification entities associated to InventoryItems that expire.
These InventoryItems are generated automatically for users, but users can edit them and set expiry date on them individually. Upon saving, if an InventoryItem has an expiry date, a Notification entity is generated and associated to it. So these Notification entities are created when an entity is updated and hence onPersist event is not going to work.
Everything seems to work fine and Notifications are generated upon saving InventoryItems as expected. Only problem is, when a Notification is created for the first time, even though it's saved properly, changes to the InventoryItem are not saved. That is a Notification with correct expiry date is created, but the expiry date is not saved on the InventoryItem.
Here's my onFlush code:
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof NotificableInterface) {
if ($entity->generatesNotification()){
$notification = $this->notificationManager->generateNotificationForEntity($entity) ;
if ( $notification ) {
$uow->persist($notification) ;
}
$entity->setNotification($notification) ;
$uow->persist($entity);
$uow->computeChangeSets();
}
}
}
}
The problem only occurs the first time a Notification is associated to an entity, i.e. the first time an expiry date is set on an InventoryItem. In later instances when expiry date is updated, the update is reflected correctly on both the Notification and InventoryItem.
Any ideas or suggestions would be appreciated.
Thanks
You need to call computeChangeset specifically on your newly created or updated entity. Just calling computeChangeSets is not enough.
$metaData = $em->getClassMetadata('Your\NameSpace\Entity\YourNotificationEntity');
$uow->computeChangeSet($metaData, $notification);
Thanks Richard. You pointed me to the right direction. I needed to recompute the change set on the parent entity (InventoryItem) to get things working properly. Additionally I had to call computeChangeSets on the unit of work to get rid of the invalid parameter number error (such as the one explained symfony2 + doctrine: modify a child entity on `onFlush`: "Invalid parameter number: number of bound variables does not match number of tokens")
Note I ended up also removing:
if ( $notification ) {
$uow->persist($notification) ;
}
Which never made sense, since I had set cascade persist on the association in my entity and should have cascaded down automatically.
My final solution is:
public function onFlush(OnFlushEventArgs $args)
{
$em = $args->getEntityManager();
$uow = $em->getUnitOfWork();
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof NotificableInterface) {
if ($entity->generatesNotification()){
$notification = $this->notificationManager->generateNotificationForEntity($entity) ;
$entity->setNotification($notification) ;
// if ( $notification ) {
// $uow->persist($notification) ;
// }
$metaData = $em->getClassMetadata(get_class($entity));
$uow->recomputeSingleEntityChangeSet($metaData, $entity);
$uow->computeChangeSets();
}
}
}
}
Let's say I have a main form (foo entity form) where I have an embedded form (bar entity embedded form).
Let's also say that foo - 1/many - bar (of course).
Now, I want to display all possible bar entities in the system, even if they aren't associated with foo. So before bind form with foo entity, I usually do some query, extract data and, if bar isn't already associated with foo, associate it (basically i create some "virtual" association that haven't to be persisted under certain circumstance. I can't use symfony2 native method as I need to handle some attributes and Symfony2 don't let me do that)
All works like a charm. Now I added to bar form a non-mapped field that should help me to know whenever to save or not the association.
Into controller I check for the presence of this field and if not, I artificially unset the index of the collection from request object. When I dump the request all is good (embedded elements without flag aren't there anymore).
BUT
When I bind request object to entity, all embedded form elements are still there. This is driving me totally cray.
Code example
(I will not paste entity code as the issue is not there. I will not paste form code also)
public function createAction()
{
$foo = new Foo();
$foo_form = $this->createForm(new FooType(), $foo);
if ($request->getMethod() == 'POST') {
$parameter_array = $request->request->all();
$bar_array = $parameter_array['foo']['bar'];
//If I dump here, of course, all bar are setted
foreach ($bar_array as $index => $bar) {
if (!isset($bar['associate'])) { //this is the flag
unset($parameter_array['foo']['bar'][$index]);
}
}
$request->request->replace($parameter_array);
//If i dump $request->request->all(); all non-flagged bar are gone
$foo_form->bind($request);
$foo->getBars(); //If I dump this all bar(s) are still there (even the not-flagged ones)
}
}
I've found a workaround. As I can't controller - or at least it seems I cannot - directly parameter bag when entities are involved, I've simply act upon object after form and object are binded.
My code is now this
public function createAction()
{
$foo = new Foo();
$foo_form = $this->createForm(new FooType(), $foo);
if ($request->getMethod() == 'POST') {
$foo_form->bind($request);
if ($foo_form->isValid()) {
$parameter_array = $request->request->all();
if (isset($parameter_array['foo']['bar'])) {
$bars = $foo->getBars();
$bar_array = $parameter_array['foo']['bar'];
foreach ($bar_array as $index => $bar) { //Of course here
if (!isset($bar['associate'])) { // I can use array_filter
$bars->remove($index); // or something similar. Is just more readable that way for this answer
}
}
$foo->setBars($bars);
}
}
}
}
Is there a better solution?
I am trying to modify a field collection in a node that already exists so I can change an image on the first element in an array of 3. The problem is, the hostEntity info is not set when I do a entity_load or entity_load_single so when I do a:
$field_collection_item->save(true); // with or without the true
// OR
$fc_wrapper->save(true); // with or without the true
I get the following error:
Exception: Unable to save a field collection item without a valid reference to a host entity. in FieldCollectionItemEntity->save()
When i print_r the field collection entity the hostEntity:protected fields are indeed empty. My field collection is setup as follows:
field_home_experts
Expert Image <--- Want to change this data only and keep the rest below
field_expert_image
Image
Expert Name
field_expert_name
Text
Expert Title
field_expert_title
Text
Here is the code I am trying to use to modify the existing nodes field collection:
$node = getNode(1352); // Get the node I want to modify
// There can be up to 3 experts, and I want to modify the image of the first expert
$updateItem = $node->field_home_experts[LANGUAGE_NONE][0];
if ($updateItem) { // Updating
// Grab the field collection that currently exists in the 0 spot
$fc_item = reset(entity_load('field_collection_item', array($updateItem)));
// Wrap the field collection entity in the field API wrapper
$fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc_item);
// Set the new image in place of the current
$fc_wrapper->field_expert_image->set((array)file_load(4316));
// Save the field collection
$fc_wrapper->save(true);
// Save the node with the new field collection (not sure this is needed)
node_save($node);
}
Any help would be greatly appreciated, I am still quite new to Drupal as a whole (end-user or developer)
Alright so I think I have figured this out, I wrote up a function that will set a field collection values:
// $node: (obj) node object returned from node_load()
// $collection: (string) can be found in drupal admin interface:
// structure > field collections > field name
// $fields: (array) see usage below
// $index: (int) the index to the element you wish to edit
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
if ($node && $collection && !empty($fields)) {
// Get the field collection ID
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
// Load the field collection with the ID from above
$entity = entity_load_single('field_collection_item', array($eid));
// Wrap the loaded field collection which makes setting/getting much easier
$node_wrapper = entity_metadata_wrapper('field_collection_item', $entity);
// Loop through our fields and set the values
foreach ($fields as $field => $data) {
$node_wrapper->{$field}->set($data);
}
// Once we have added all the values we wish to change then we need to
// save. This will modify the node and does not require node_save() so
// at this point be sure it is all correct as this will save directly
// to a published node
$node_wrapper->save(true);
}
}
USAGE:
// id of the node you wish to modify
$node = node_load(123);
// Call our function with the node to modify, the field collection machine name
// and an array setup as collection_field_name => value_you_want_to_set
// collection_field_name can be found in the admin interface:
// structure > field collections > manage fields
updateFieldCollection(
$node,
'field_home_experts',
array (
'field_expert_image' => (array)file_load(582), // Loads up an existing image
'field_expert_name' => 'Some Guy',
'field_expert_title' => 'Some Title',
)
);
Hope this helps someone else as I spent a whole day trying to get this to work (hopefully I won't be a noob forever in Drupal7). There may be an issue getting formatted text to set() properly but I am not sure what that is at this time, so just keep that in mind (if you have a field that has a format of filtered_html for example, not sure that will set correctly without doing something else).
Good luck!
Jake
I was still getting the error, mentioned in the question, after using the above function.
This is what worked for me:
function updateFieldCollection($node, $collection, $fields = Array(), $index = 0) {
$eid = $node->{$collection}[LANGUAGE_NONE][$index]['value'];
$fc_item = entity_load('field_collection_item', array($eid));
foreach ($fields as $field => $data) {
$fc_item[$eid]->{$field}[LANGUAGE_NONE][0]['value'] = $data;
}
$fc_item[$eid]->save(TRUE);
}
I hope this helps someone as it took me quite some time to get this working.
Seems doesn't works:
<?php
$collection = Mage::getModel('catalog/product')->getCollection();
foreach($collection->getItems() as $key => $_product){
//product
$collection->removeItemByKey($key);
}
?>
$collection is still populated
If you'd like to work with an empty collection, the best approach would be to load it with a filter that would always produce an empty set. Here's an example:
$collection = Mage::getModel('catalog/product')->getCollection()
->addFieldToFilter('entity_id', 0);
Because Magento product ids start at 1, this collection would remain empty, until you add items to it with the addItem() method.
clear() and removeItemByKey(), on the other hand, will only trigger a second run to the database to refetch the data you don't want in there.
You question doesn't make sense. Running the following code
$c = Mage::getModel('catalog/product')->getCollection();
foreach($c->getItems() as $key=>$item)
{
$c->removeItemByKey($key);
}
foreach($c->getItems() as $key=>$item)
{
var_dump($key);
}
var_dump( "Done" );
results in only the word "done" being output (Magento 1.6.1).
My guess it something about your installation of Magento is making the call to $c->getItems(); trigger a reload of the collection. So, you remove all the items, but then when you call your second getItems, the collection is refetched.
There's clear() method in Varien_Data_Collection class which clears the collection.
I'm not sure if the method exists in the time the question was asked, but it exists in Magento 1.7
There's also a possibility to remove all the items without "fake loading" (in opposite to Shay Acrich's answer):
class MyCollection extends SomeCollection {
// ...
public function setEmpty()
{
$this->clear();
$this->_totalRecords = 0;
$this->_setIsLoaded(true);
return $this;
}
// ...
}
Setting _totalRecords to 0 is required in order not to allow getSize() method to reload the collection.
Nevertheless, one needs to extend / modify a collection's code, because both the field _totalRecords and the method _setIsLoaded() are protected.
There should be noted, that if a particular collection ignores flags like _totalRecords and _isCollectionLoaded the above solution may not work as expected.
$collection->clear()
should do the work.