Magento – Programatic Quote, Order, Invoices

<?php
 
require_once 'app/Mage.php';
 
Mage::app('default');
 
$store = Mage::app()->getStore('default');
 
$customer = Mage::getModel('customer/customer');
$customer->setStore($store);
$customer->loadByEmail('email_address@gmail.com');
 
$quote = Mage::getModel('sales/quote');
$quote->setStore($store);
$quote->assignCustomer($customer);
 
$product1 = Mage::getModel('catalog/product')->load(166); /* HTC Touch Diamond */
$buyInfo1 = array('qty' => 1);
 
$product2 = Mage::getModel('catalog/product')->load(18); /* Sony Ericsson W810i */
$buyInfo2 = array('qty' => 3);
 
$quote->addProduct($product1, new Varien_Object($buyInfo1));
$quote->addProduct($product2, new Varien_Object($buyInfo2));
 
$billingAddress = $quote->getBillingAddress()->addData($customer->getPrimaryBillingAddress());
$shippingAddress = $quote->getShippingAddress()->addData($customer->getPrimaryShippingAddress());
 
$shippingAddress->setCollectShippingRates(true)->collectShippingRates()
                ->setShippingMethod('flatrate_flatrate')
                ->setPaymentMethod('checkmo');
 
$quote->getPayment()->importData(array('method' => 'checkmo'));
 
$quote->collectTotals()->save();
 
$service = Mage::getModel('sales/service_quote', $quote);
$service->submitAll();
$order = $service->getOrder();
 
$invoice = Mage::getModel('sales/service_order', $order)->prepareInvoice();
$invoice->setRequestedCaptureCase(Mage_Sales_Model_Order_Invoice::CAPTURE_ONLINE);
$invoice->register();
 
$transaction = Mage::getModel('core/resource_transaction')
                    ->addObject($invoice)
                    ->addObject($invoice->getOrder());
 
$transaction->save();
  • The $invoice->register() calls pay() in the Mage_Sales_Model_Order_Invoice class. This fires the event sales_order_invoice_pay which is useful for hooking into after an invoice has been paid for; E.g. automatically sending the invoice email upon creation:
   public function sendEmail(Varien_Event_Observer $observer)
    {
        $event = $observer->getEvent();
        /** @var Mage_Sales_Model_Order_Invoice $invoice */
        $invoice = $event->getInvoice();
        if(!$invoice->getEmailSent()){
            $invoice->sendEmail(true);
        }
    }

Simplified Magento Flow with Events

The Mage_Core_Controller_Varien_Front is instantiated. This is the class which is always used despite its front name. It picks which routers should be used.

Event: controller_front_init_before
Event: controller_front_init_routers

Mage_Core_Controller_Varien_Front → dispatch() → Router Match Loop. The standard routers are:

  • Mage_Core_Controller_Varien_Router_Standard (Frontend)
  • Mage_Core_Controller_Varien_Router_Admin (Admin)
  • Mage_Core_Controller_Varien_Router_Default (Default)

When a router matches in the loop, its job is to set the following on the request:

$request->setModuleName($module);
$request->setControllerName($controller)
$request->setActionName($action)
$request->setControllerModule($realModule)
$request->setDispatched(true);

And then calls dispatch on the instance of the controller (Eg. Mage_Cms_IndexController).

Dispatch is defined in Mage_Core_Controller_Varien_Action, which is extended by Mage_Core_Controller_Front_Action, which should be extended by custom controllers on the frontend.

PreDispatch

The dispatch method calls predispatch in the controller. To not dispatch the controller, I.e to forward to another controller at this point, set:

$request->setDispatched(false);

And the controller’s action method will not be called. To switch controller, action, etc, change the values on the request object, and the router’s match loop will continue and attempt to dispatch the new controller. The predispatch method then fires:

Event: controller_action_predispatch
Event: controller_action_predispatch_ + getRouteName()
Event: controller_action_predispatch_ + getFullActionName()

These events can also be used to hook into, and forward to another controller if required. At this point, no blocks are created – the layout cannot be manipulated. If the request’s isDispatched method, remains as true, then the flow continues, and the controller’s action method is executed.

LoadLayout

The following two methods (loadLayout and renderLayout) must be called from the custom controller’s action method for these to apply

 $controller->loadLayout($handles)

is called with the option to supply handles. These replace the ‘default’ handle, so if using this, pass default in your handles array. Note: These handles will be added before any action layout handles, so the instructions within them may be overwritten.

addActionLayoutHandles()

adds the STORE_code layout handle, theme handle (THEME_*), and the full action name in lowercase (E.g. customer_account_index)

loadLayoutUpdates()

Event: controller_action_layout_load_before
This is the best event to use to add custom layout handles, however it will fire for every controller. See this for workarounds.

generateLayoutXml()

Event: controller_action_layout_generate_xml_before

generateLayoutBlocks()

Event: controller_action_layout_generate_blocks_before
The XML has been generated at this point, not any actual layout blocks. If required, modifications to the XML could be made at this point.

$layout->generateBlocks()

The blocks are now created. Each block fires:
Event: core_block_abstract_prepare_layout_before
before the block’s _prepareLayout() is called, followed by
*Event:* core_block_abstract_prepare_layout_after

Event: controller_action_layout_generate_blocks_after
This is where blocks can programatically be inserted into the layout.

RenderLayout

Event: controller_action_layout_render_before
Event: controller_action_layout_render_before_ + fullActionName
where blocks can also be added to the layout programatically, however a reference to the layout object is not passed in the dispatchd observer, so the layout must be retrieved using Mage::app()->getLayout()

PostDispatch

The controller’s postDispatch() method is then called, firing the following events:

Event: controller_action_postdispatch
Event: controller_action_postdispatch_ + $this->getFullActionName()
Event: controller_action_postdispatch_ + $this->getRouteName()