8

Can anyone explain the difference between these events. Just the quick and dirty please. Thank you.

I have an Observer method like so:

public function detectProductChanges($observer)
    {
        $product = $observer->getProduct();
        $old = $product->getOrigData();
        $new = $product->getData();
        if ($product->hasDataChanges() && $old['status'] == 1 && $new['status'] == 2) {
            $this->_sendStatusMail($product);
        }
    }

It's not getting to the sendStatusMail()

I'm hooking into the event:

        <events>
            <catalog_product_save_after>
                <observers>
                    <productchange>
                        <type>singleton</type>
                        <class>A_ProductNotification_Model_Observer</class>
                        <method>detectProductChanges</method>
                    </productchange>
                </observers>
            </catalog_product_save_after>
        </events>

Should i be using: catalog_product_save_commit_after

GOAL:

Have an email sent after product is disabled.

private function _sendStatusMail($product)
    {
        if (!Mage::getStoreConfig('trans_email/ident_custom3/email')) return false;
        $emailTemplate = Mage::getModel('core/email_template');
        $emailTemplate->loadDefault('elec_productnotification_tpl');
        $emailTemplate->setTemplateSubject('Product has been disabled');
        $emailTemplate->setSenderEmail($salesData['email']);
        $emailTemplateVariables['style_number']   = $product->getElecStyle();
        $emailTemplateVariables['frame_color']    = $product->getAttributeText('frame_color');
        $emailTemplateVariables['size']           = $product->getAttributeText('size');
        $emailTemplateVariables['elec_color'] = $product->getAttributeText('elec_color');
        $emailTemplateVariables['store_name']   = Mage::getModel('core/store')->load($product->getStoreId())->getName();
        $emailTemplateVariables['product_name'] = Mage::getModel('catalog/product')->load($product->getId())->getName();
        $emailTemplateVariables['product_sku']  = $product->getSku();
        $emailTemplateVariables['dates']        = date("F jS Y h:i:sA", strtotime('-7 hours'));
        // Get General email address (Admin->Configuration->General->Store Email Addresses)
        $emails = explode(',', Mage::getStoreConfig('trans_email/ident_custom3/email'));
        foreach ($emails as $email) $emailTemplate->send($email, $product->getStoreId(), $emailTemplateVariables);
    }
}
2
  • you should use event <catalog_product_status_update>
    – Nickool
    Jun 8, 2016 at 19:17
  • Is that the reason it's not firing? Simply using the wrong event? @Nickool
    – thismethod
    Jun 8, 2016 at 19:26

2 Answers 2

14

Saving happens in a MySQL transaction and the save_after event is triggered before the transaction is committed, so that you can do additional updates in the database within the same transaction.

The save_commit_after event is triggered after the transaction has been committed, i.e. when the changes were written to the database.

Also, on save_commit_after, the _hasDataChanges property already has been reset to false, so your check would not work. On the other hand, if there were no changes, both events would not even be triggered, because Mage_Core_Model_Abstract::save() does nothing if there were no data changes:

if (!$this->_hasModelChanged()) {
    return $this;
}

That being said, I don't see why your code should not work.

5
  • Thank you for the answer. When i add a Mage::log() in place of sendStatusMail() i get the log message correctly. But it's not sending emails. I ensured that "Disable Email Communications" is set to NO and that my email address is in my custom email > store email addresses. Any other ideas why it's not working? @fschmengler
    – thismethod
    Jun 10, 2016 at 19:23
  • Without knowing your sendStatusMail method, no. That's probably material for another question. Or does the same method work if called from a different context? Jun 10, 2016 at 20:00
  • I updated my original question to show the sendStatusMail method. If you don't mind further assisting. Thank you.
    – thismethod
    Jun 10, 2016 at 20:34
  • Any chance you could give me your opinion on my sendStatusMail($product) method?
    – thismethod
    Jun 13, 2016 at 19:18
  • I can't spot any errors there, sorry Jun 13, 2016 at 20:50
1

vendor/magento/framework/Model/ResourceModel/Db/AbstractDb.php

public function save(\Magento\Framework\Model\AbstractModel $object)
{
    // ...

    $this->beginTransaction();

    try {
        // ...
        if ($object->isSaveAllowed()) {
            // ...
            $this->_beforeSave($object);
            // ...
            if ($this->isObjectNotNew($object)) {
                $this->updateObject($object);
            } else {
                $this->saveNewObject($object);
            }
            // ...
            $this->processAfterSaves($object);
        }
        $this->addCommitCallback([$object, 'afterCommitCallback'])->commit();
        // ...
    } catch (\Exception $e) {
        $this->rollBack();
        $object->setHasDataChanges(true);
        throw $e;
    }
    return $this;
}

Let's have a look at saving product entity.

-product_model save
|-product_resource save
|--begin transaction (0 lvl)
|---before product save events
|---creating new product or updating existing one
|---after product save events
|----one of event is saving another entity CatalogInventory Stock
|-----catalog_inventory_stock resource save
|------begin another transaction (1 lvl)
|-------before stock save events
|-------updating / creating stock item
|-------after product save events (here could be one more 
        dependable entity which could cause one more save
        operation and begin another transaction)
|------commit of 1st level !!! No callbacks executed
|--commit of 0 level ALL CALLBACKS ARE EXECUTED

Here is the code of commit function:

/**
 * Commit resource transaction
 *
 * @return $this
 * @api
 */
public function commit()
{
    $this->getConnection()->commit();
    /**
     * Process after commit callbacks
     */
    if ($this->getConnection()->getTransactionLevel() === 0) {
        $callbacks = CallbackPool::get(spl_object_hash($this->getConnection()));
        try {
            foreach ($callbacks as $callback) {
                call_user_func($callback);
            }
        } catch (\Exception $e) {
            $this->getLogger()->critical($e);
        }
    }
    return $this;
}

Let's have a look at our example more closely.

  1. $this->getConnection()->commit(); put values into DB for our 1st level (it's Stock). If something bad happens here, exception will be thrown and all changes will be rolled back.

  2. Then it goes to process callbacks. As we are currently at 1st level, no callbacks will be called. And we are moving out from catalog_product_after_save event to commit product changes (0 level).

  3. $this->getConnection()->commit(); put values into DB for our 0 level (it's Product itself). If something bad happens here exception will also be thrown and all changes also will be rolled back.

  4. Then we are moving to callbacks execution. Now we are at 0 level and callbacks will be run. Anything you bad inside call_user_func($callback); will be catched up and just logged. Nothing will be rolled back if callback cause an exception

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.