4

Background

I have an entity that's directly linked to Magento shipments with three fields:

  • entity_id
  • data
  • shipment_id

Problem

This entity is created based on a condition in an observer on the sales_order_shipment_save_before event. Something that looks like this:

if (condition) {
    // Create my entity and keep going
}
else {
    // Throw an exception and stop the Magento shipment creation
}

As you can see, if the condition is not matched, it must stop generating the Magento shipment. However, using this event, I cannot get the Magento shipment id using $shipment->getIncrementId(), because it's a before event.

If I try to use the sales_order_shipment_save_after event, I am able to get the increment id. However, I cannot stop the creation of the Magento shipment as it already has been created.

Temporary fix

A hacky way of fixing my issue is to get the next increment id by calling some code like this:

$sql = sprintf("SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_name = 'sales_flat_shipment' and table_schema = '%s'",
               Mage::getConfig()->getNode('global/resources/default_setup/connection/dbname'));
$readConn = Mage::getSingleton('core/resource')->getConnection('core_read');
$results = $readConn->fetchAll($sql);
$nextIncrementId = $results[0]['AUTO_INCREMENT'];

But this could possibly run into a race condition.

So how can I fix my problem without running into a race condition?

  • Rather than deleting it could your custom development instead be to add a valid/invalid flag to the shipment or similar? This would avoid your chicken egg problem. what's the actual business problem you're trying to solve as it's a bit too abstract for me to understand atm :) – Luke Rodgers Mar 23 '17 at 9:47
  • @LukeRodgers I'm basically creating shipments (not Magento shipments) that are used to call a delivery service API. I want those API shipments to be created only if the Magento shipment is valid, and if the Magento shipment is not valid, stop creating the Magento shipment and thus not create the API shipment. Does that make sense ? – Raphael at Digital Pianism Mar 23 '17 at 9:56
4

I don't think any of the both events can do the trick by itself. As long as you don't need that entity_id for the condition before the Magento shipping kicks in, you can use both events to make it work:

  • sales_order_shipment_save_before: Do you checks in the condition and if they succeed, let them go through. Save all information within the observer.

  • sales_order_shipment_save_after: Save the id that was generated in the Magento shipping and save all additional information you have saved before within the observer.

To make that work, you have to make sure the observer is loaded as a Singleton object, because otherwise the information will be gone as soon as it's recreated during the event. To me this solution is much better since it doesn't use any hacks to make it work, even though it comes with that additional complexity of another event.

  • 2
    I dont see why it needs to be a singleton? Simply do the check in the _before server to throw the exception. And then in the _after observer you are guaranteed that your check has passed and you can safely create your second entity without issue? – Luke Rodgers Mar 23 '17 at 10:13
  • That's right. But in case you are developing something within the before you'd have to do it again in after. That's kind of what we were discussing in Slack. If it's not a big deal to recalculate whatever, you are absolutely right. On the other hand, I don't see a lot of reasons why it shouldn't be a Singleton, which is the default behavior and you don't have the additional effort of recreating the object. And sure, you could also use the registry for stuff like that but I consider global state as not intended in any case. – Michael Türk Mar 23 '17 at 10:23
  • From my understanding no objects actually need built or instantiated in the _before, its simply the check and the exception thrown. You can then safely to the full object instantiation and persistence in the _after without any worries. If by "calculate" you mean the $condition statement, i don't think it would be needed in the _after as it was safely executed in the _before. Global state has its uses, I just think it's over engineering in this instance :) – Luke Rodgers Mar 23 '17 at 10:26
2

I didn't look really deep, but I saw this in save():

        $this->_beforeSave();
        if ($this->_dataSaveAllowed) {
            $this->_getResource()->save($this);
            $this->_afterSave();
        }

There's no setter for $_dataSaveAllowed.

Maybe extend the shipment model to add a setter for this attribute and set it to false in your observer?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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