Codegento Who Let Mage Out Of The Cage?

6Mar/113

The “Model” Family

Posted by Ben Robie

With any framework that follows MVC, you should find a place where models are defined, along with some form of the "resource model". In Magento, you will find three members of the "model" family. The Model, the Resource Model, and the Collection Model. All three members of the family work together to get us the data we need, when we need it.

We have talked about creating a model before, so we won't get into much of that, but we WILL get into a lot about the resource models and collections.

As I see it:
A "model" is used to store data, and perhaps perform some business logic against that data.
A "resource model" is used to interact with the database (or perhaps other forms of persistent data) on behalf of the "model". The "resource model" is the one actually performing your CRUD operations.
A "collection model" holds 1 to many "models" and knows how to tell the "resource model" to get a bunch of rows based on information it is given.

Below, is my attempt to illustrate how they are all tied together, and some useful tidbits of information about using the "model family":

As in most things within Magento, this whole thing starts with the config.xml. It is in here that we define where to look for our models, resource models, and what tables our resource models will be interacting with. Here is the snippet of the config.xml that defines these things:

 <global>
    	<models>
            <awesome>
                <class>Super_Awesome_Model</class>
                <resourceModel>awesome_mysql4</resourceModel>
            </awesome>
            <core>
            	<rewrite>
            		<translate>Super_Awesome_Model_Translate</translate>
            	</rewrite>
             </core>
             <awesome_mysql4>
                <class>Super_Awesome_Model_Mysql4</class>
                <entities>
                    <example>
                        <table>super_awesome_example</table>
                    </example>
                </entities>
            </awesome_mysql4>
        </models>

        <resources>
            <awesome_setup>
                <setup>
                    <module>Super_Awesome</module>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </awesome_setup>
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>
</global>

Here is the folder/file structure with the classes we will be using:

Super
  |_ Awesome
        |_Model
            |_Mysql4
            |    |_Example
            |    |    |_Collection.php (Super_Awesome_Model_Mysql4_Example_Collection)
            |    |_Example.php (Super_Awesome_Model_Mysql4_Example)
            |_Example.php (Super_Awesome_Model_Example)

Super_Awesome_Model_Example is the "model"
Super_Awesome_Model_Mysql4_Example is the "resource model"
Super_Awesome_Model_Mysql4_Example_Collection is the "collection model"

What I will do now is walk through some of the normal uses of these models and now the code and config ties together:

When you create an object with Mage::getModel('awesome/example'), a few things are happening:
1. We go to the config.xml to see where we should look for the "awesome" models.
2. Once we have figured how where to find the model, we create a new instance of it. In our example, we will end up doing a "new Super_Awesome_Model_Example()".
3. When the class is instantiated, it will, by default, call the constructor. Here is where we first tie together our "model" and "resource model.

class Super_Awesome_Model_Example extends Mage_Core_Model_Abstract
{
	protected function _construct()
	{
		parent::_construct();
		$this->_init('awesome/example');
	}
}

3a. When the _init('awesome/example') is called, we are actually passing in the class alias to the "resource model". It gets a little confusing, because it looks the exactly the same as the "model" class alias. However, in the _init() it, by default, only calls the _setResourceModel() passing through what you passed the _init().
3b. The _setResourceModel() simply sets the aliases for the "resource model" AND the "collection model" into the "model".

 protected function _setResourceModel($resourceName, $resourceCollectionName=null)
    {
        $this->_resourceName = $resourceName;
        if (is_null($resourceCollectionName)) {
            $resourceCollectionName = $resourceName.'_collection';
        }
        $this->_resourceCollectionName = $resourceCollectionName;
    }

At this point the Mage::getModel('awesome/example') is effectively finished. Now we are going to try to USE this "model". Typically, after you have created an instance of the "model", you will load it up with some data from the database. To do this, as the most basic level, we call the load() function.

For example:

$example = Mage::getModel('awesome/example');
$example->load(1);

Here is where the "rubber meets the road". The "model's" load method, for all intensive purposes, hands its duties off to the "resource model's" load() function, passing itself along the way:

$this->_getResource()->load($this, $id, $field);

There are a few things of note in the "one-liner" above. When you call the _getResource() function, it uses the class alias for the "resource model" that we set in the constructor to create the class. You can follow the logic all the way through, but in a nutshell, Magento takes 'awesome/example', splits it apart and uses 'awesome' to find the <resourceModel> element we defined in the config.xml. It then puts everything back together to be 'awesome_mysql4/example'. It then falls into the much used Mage_Core_Model_Config->getGroupedClassName() method where it deconstructs 'awesome_mysql4/example', and reconstructs it into Super_Awesome_Model_Mysql4_Example by using the <awesome_mysql4> element in the config.xml.

As a part of the _getResource() it is calling the constructor of the Super_Awesome_Model_Mysql4_Example class. This constructor is minimal, but does some important stuff:

class Super_Awesome_Model_Mysql4_Example extends Mage_Core_Model_Mysql4_Abstract
{
	protected function _construct()
	{
		$this->_init('awesome/example', 'id');
	}
}

The _init() function of the "resource model" sets the main table and the ID of that table to be used to fetch data. Notice that the main table variable is an alias to that table that has been configured in the config.xml, in particular:

            <awesome_mysql4>
                <class>Super_Awesome_Model_Mysql4</class>
                <entities>
                    <example>
                        <table>super_awesome_example</table>
                    </example>
                </entities>
            </awesome_mysql4>

The functions that are called, once again, use 'awesome' to find 'awesome_mysql4', and then they look inside the <awesome_mysql4> element to find an entity called 'example', and then find its <table>.

Although there are just a few pieces to the puzzle, THEY MUST ALL BE CONFIGURED PERFECTLY FOR IT ALL TO WORK TOGETHER.

Ok, so we are back to calling the ->load() function on the "resource model". The load() function uses the ID value passed in to generate a SQL statement (using, also, the main table and ID field) to query the database. The configuration of note here is the "read" and "write" adapters. I have never used anything but the "core" adapters, so the following configuration was the only part needed:

        <resources>
           ...
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>

The load() then uses the SQL to fetch the data from the database and store it into the "model" instance that was passed in.

** Deep breath **

Now we have a "model" instance full of data. We can then change this data (using the magic getters and setters) and simply call the save() function on the "model". This uses the "resource model" to save the data to the database (automatically figuring out if it should be an INSERT or an UPDATE).

Now how in the heck do we use a "collection model"? Easy.

After we have an instance of the Super_Awesome_Model_Example "model" we can just call the getCollection() function on it. This uses the class alias for the collection that we stored (back when we called the constructor) to get an instance of the "collection model".

One thing that confused me at first was that getCollection() only returned an EMPTY instance of the "collection model"; there was no data in it. To get the data, we need to call the load() function on the "collection model". This can be done directly, or indirectly through the getItems() function. After the load() function is called, the $_items variable is now populated with a bunch of data-filled "models".

HINT: If you are trying to figure out what SQL is running when you call the load() on the collection, you can pass in ->load(true, true) and it will print the SQL to the screen AND a log file.

NOTE: If the collection is a part of the EAV structure, the "models" will ONLY have data from the entity table. To get the rest of the data, you will need to call the load() function on each model, passing in its own ID.

The benefit of using the "resource models" and "collection models" are it's ubber-rich helper functions for generating the SQL. Below, I will list a few of the helpful functions and give a super brief description of what they do. Note, some of these functions are up in the parents/grand parents of the "resource model" and "collection model" classes.

Varien_Data_Collection_Db->addFieldToFilter($field, $condition=null)

$collection->addFieldToFilter('name', 'example1');
$collection->addFieldToFilter('name', array('like' => 'example%'));
$collection->addFieldToFilter('name', array('in' => array('example1', 'example2')));

Mage_Eav_Model_Entity_Collection_Abstract->addAttributeToFilter($attribute, $condition=null, $joinType='inner')

$collection->addAttributeToFilter('color', 'blue');

Mage_Core_Model_Mysql4_Collection_Abstract->join($table, $cond, $cols='*')

$collection->join('other_table',  'e.id = other_table.fid', array('column1', 'column2'));

Varien_Data_Collection_Db->addOrder($field, $direction = self::SORT_ORDER_DESC)

$collection->addOrder('sequence', 'ASC');

One final thing to say...

There are events that are dispatched before and after EVERY load, save, and delete in both the "models" and the "resource models". This should give you PLENTY of places to hook in if you should need to change the way a specific table needs to be interacted with.

27Feb/114

Install Scripts and Upgrade Scripts

Posted by Soumya Shetty

Install scripts are a way provided by Magento to systematically track and version database changes. The changes can vary from schema changes like adding and dropping of tables, columns and even data. It is a good practice to write install scripts for any database changes made by your application. In this article i will talk about creating an install script, execution of the install scripts by Magento and writing upgrade scripts.

To demonstrate the creation and execution of install scripts lets take a simple example. Assume we have a custom module in our app/code/local/Company/ directory which keeps track of who and where a particular product was purchased from. Let the module name be Track. To keep track of this information we need a new table in our database. Let us see how to write an install script which does the creation of a new table in the Magento database.

Steps on creating an install script:

  1. Create a setup folder called track_setup under app/code/local/Company/Track/sql.
  2. Create an install script called mysql4-install-0.1.0.php under the setup directory where 0.1.0 is the version number.
    <?php
    
    $installer = $this;
    
    $installer->startSetup();
    
    $installer->run("
    
    -- DROP TABLE IF EXISTS {$this->getTable('customer_track')};
    CREATE TABLE IF NOT EXISTS {$this->getTable('customer_track')} (
    `id` INT UNSIGNED NOT NULL ,
    `sku` varchar(64) NOT NULL ,
    `customer_id` INT NOT NULL ,
    `region` varchar(255) NOT NULL ,
    `country_id` char(2) NOT NULL ,
    ) ;
    
    ");
    
    $installer->endSetup();
    
  3. Create a setup file under app/code/local/Company/Track/Model/Mysql4
    ?php
    
    /**
    * Track Setup
    *
    * @category Company
    * @package  Company_Track
    * @author
    */
    class Company_Track_Model_Mysql4_Setup extends Mage_Catalog_Model_Resource_Eav_Mysql4_Setup
    {
    }
    
  4. The config.xml contains two entries which tell magento about where to find the install scripts and what is the latest version of the script for the module
    • Entry which tells magento the setup directory which holds the install scripts:
      <global>
          <resources>
              <track_setup>
                  <setup>
                      <module>Company_Track</module>
                      <class>Company_Track_Model_Mysql4_Setup</class>
                  </setup>
              </track_setup>
          </resources>
      </global>
      
    • Entry which tells magento about the latest install script version
      <modules>
          <Company_Track>
              <version>0.1.0</version>
         </Company_Track>
      </modules>
      

Steps to run the install script:

Magento keeps tracks of the latest install script that was run for a module in the core_resource table. To make sure that magento picks up the changes in the config.xml file you need to clear the cache and then hit the home page. Magento looks for any new install scripts and compares the version to the version number in the core_resource table and if it greater than the later it runs it.

To make sure your install script ran go to the database and verify that in the core_resource table an entry of track_setup exists with the version number of the install script.

Upgrade scripts:
The first script written for a module is usually an install script. Any consecutive script written for a module is called an upgrade script. The script is very similar to an install script but it is named differently.
The script is named as mysql4-upgrade-0.1.0-0.1.1.php which tells magento the order in which the scripts need to run.

Install script for adding a new attribute:

<?php
$installer = $this;
/* $installer Services_Issue_Model_Mysql4_Setup */

$installer->addAttribute('catalog_product', 'custom_price', array(
'backend'       => '',
'frontend'      => '',
'label'         => 'Custom Price',
'note'          => 'Custom Price.',
'input'         => 'text',
'frontend_class'    => 'required_entry validate-greater-than-zero  validate-digits',
'source'        => '',
'global'        => true,
'visible'       => true,
'required'      => true,
'user_defined'  => false,
'default'       => '',
'visible_on_front' => false,
'apply_to'        => 'simple',

));

The above install script adds a new attribute to catalog products canned custom_price. The frontend_class tells magento what type of validation are applicable on the frontend for the attribute. The apply_to field defines which product types the attribute is applicable to.

23Feb/110

Writing a PHPUnit Test Against Magento

Posted by Jason Evans

We write PHPUnit tests for all the Models, Blocks and Helpers we write. The primary benefit of doing this is to prove a method works as expected.  This allows us to refactor code and prove the method still works as expected (i.e. regression test).  Another benefit is being able to run a full automated regression test after upgrading Magento.  In this post I will only be covering the writing a simple PHPUnit test for a model class. Note: We have not written unit tests for Magento controllers. But we are looking into using the Mage_Test extension to allow us to write unit tests for controllers.

In order to run a PHPUnit test against a Magento class we will need to create an extension of the PHPUnit_Framework_TestCase class.  This class will override the setup method to call Mage::app().

abstract class Magento_PHPUnit_Framework_TestCase extends PHPUnit_Framework_TestCase {
    public function setUp() {
        parent::setUp();
        Mage::app();
    }
}

We will be writing a unit test for the method getName() in the class Super_Awesome_Model_Customer.

class Super_Awesome_Model_Customer extends Mage_Customer_Model_Customer {

    public function getName() {
        $name = parent::getName();
        $name = $name . ' loves Magento';
        return $name;
    }

}

We will create a unit test extending the Magento_PHPUnit_Framework_TestCase.. This unit test will assert the getName() method appends the string ' loves Magento' to the customer's name.

class Super_Awesome_Model_CustomerTest extends Magento_PHPUnit_Framework_TestCase
{
    public function setUp() {
        parent::setUp();
        $this->myModel = new Super_Awesome_Model_Customer();
    }

    public function testGetName() {
        $result = $this->myModel->getName();
        $this->assertStringEndsWith(' loves Magento', $result);
    }

}

You can now run your unit test using PHPUnit. This post will not cover how to install and use PHPUnit. Click here for more information about using PHPUnit.. If you are using Eclipse PDT for your development environment. I highly recommend using the Eclipse plugin MakeGood. The MakeGood plugin allows you to easily debug unit tests and easily navigate to the failing test.

Once you start writing unit tests against Magento models you will need to write a test using the database. This will require you to either put the database in a predictable state between test runs or mock the database interaction. The techniques I have used in PHPUnit are the DbUnit extension and mock objects. Ideally, I would choose to mock the entire database layer. This would be the easiest to maintain and the tests would run faster compared to inserting data into the database. However this would require a significate coding effort. Instead we have taken advantage of DbUnit to setup the database. This has worked very well for us. In the future I may expand on how to use DbUnit for unit testing Magento classes.

22Feb/1133

Grids and Forms in the Admin Panel

Posted by Ben Robie

In my humble opinion, creating new sections in the Admin Panel are a tad bit more complicated than creating new features on the frontend. Hopefully, this post will help get you a few steps closer to being able to understand and create adminhtml grids and forms.

The first thing you need to do is create a menu item to get to your new grid, then you begin the exciting journey into some Adminhtml action.

The Config
How about I just throw the whole stinkin' config.xml file out here right off the bat:

<config>
    <modules>
        <Super_Awesome>
            <version>0.1.0</version>
        </Super_Awesome>
    </modules>
    <adminhtml>
        <!-- The <layout> updates allow us to define our block layouts in a seperate file so are aren't messin' with the magento layout files.  -->
    	<layout>
			<updates>
				<awesome>
					<file>awesome.xml</file>
				</awesome>
			</updates>
		</layout>
		<!-- The <acl> section is for access control. Here we define the pieces where access can be controlled within a role. -->
		<acl>
			<resources>
				<admin>
					<children>
						<awesome>
							<title>Awesome Menu Item</title>
							<children>
								<example translate="title" module="awesome">
									<title>Example Menu Item</title>
								</example>
							</children>
						</awesome>
					</children>
				</admin>
			</resources>
		</acl>
	</adminhtml>
	<admin>
		<!--
			Here we are telling the Magento router to look for the controllers in the Super_Awesome_controllers_Adminhtml before we look in the
			Mage_Adminhtml module for all urls that begin with /admin/controller_name
		 -->
		<routers>
			<adminhtml>
				<args>
					<modules>
						<awesome before="Mage_Adminhtml">Super_Awesome_Adminhtml</awesome>
					</modules>
				</args>
			</adminhtml>
		</routers>
	</admin>

    <global>
    	<models>
            <awesome>
                <class>Super_Awesome_Model</class>
                <resourceModel>awesome_mysql4</resourceModel>
            </awesome>
             <awesome_mysql4>
                <class>Super_Awesome_Model_Mysql4</class>
                <entities>
                    <example>
                        <table>Super_Awesome_example</table>
                    </example>
                </entities>
            </awesome_mysql4>
        </models>

        <resources>
            <awesome_setup>
                <setup>
                    <module>Super_Awesome</module>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </awesome_setup>
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>

   	 	<blocks>
            <awesome>
                <class>Super_Awesome_Block</class>
            </awesome>
        </blocks>
        <helpers>
            <awesome>
                <class>Super_Awesome_Helper</class>
            </awesome>
        </helpers>
    </global>
</config>

Some things of note about the config.xml:

  1. There is a layout file defined (awesome.xml)
  2. There is a change to the adminhtml router that tells the route to look in our module before looking into Mage_Adminhtml
  3. Everything else is pretty straight-forward (models, resource models, collections, setup, blocks, helpers...)

The Layout
Before I forget, here is the contents of the layout file (design/adminhtml/default/default/layout/awesome.xml):

<?xml version="1.0"?>

<layout>
    <adminhtml_example_index>
		<reference name="content">
			<block type="awesome/adminhtml_example" name="example" />
		</reference>
	</adminhtml_example_index>

	 <adminhtml_example_edit>
		<reference name="content">
			<block type="awesome/adminhtml_example_edit" name="example_edit" />
		</reference>
	</adminhtml_example_edit>

</layout>

We don't need to define much in here. The reason I think that Admin Panel coding is a little more complicated is because there is a lot of things that happen behind the scenes that are not driven by the layout as we normally see it.

Install Script
For testing/example purposes, I also created an install script to create a table and load up some data. In the sql/awesome_setup/mysql4-install-0.1.0.php file, I have:

<?php

$installer = $this;

$installer->startSetup();

$installer->run("

-- DROP TABLE IF EXISTS {$this->getTable('super_awesome_example')};
CREATE TABLE {$this->getTable('super_awesome_example')} (
  `id` int(11) unsigned NOT NULL auto_increment,
  `name` varchar(100) NOT NULL,
  `description` varchar(100) NOT NULL,
  `other` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 1', 'Example One Description', 'This first example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 2', 'Example Two Description', 'This second example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 3', 'Example Three Description', 'This third example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 4', 'Example Four Description', 'This fourth example is reall awesome.');

");

$installer->endSetup();

The Models
In this example, I am going to assume you know how to create a model, its resource model, and its collection model. I created the following classes:
Super_Awesome_Model_Example
Super_Awesome_Model_Mysql4_Example
Super_Awesome_Model_Mysql4_Example_Collection

The Controller
Hopefully, we all know what a controller does, so I won't explain that part of the MVC pattern. The adminhtml controllers generally provide actions to do basic CRUD operations on the model. In ours, you will find the following actions:

  • index - Shows the grid.
  • edit - Shows the edit/new form.
  • save - Saves the form data.
  • delete - Deletes the model.
  • new - Forwards on to the edit action

There really isn't anything crazy going on here, so I would just take a few minutes to read through the code and get an understanding of what each function does (and does not do):

<?php

class Super_Awesome_Adminhtml_ExampleController extends Mage_Adminhtml_Controller_Action
{

	public function indexAction()
	{

		$this->loadLayout();
		$this->renderLayout();
	}

	public function newAction()
	{
		$this->_forward('edit');
	}

	public function editAction()
	{
		$id = $this->getRequest()->getParam('id', null);
		$model = Mage::getModel('awesome/example');
		if ($id) {
			$model->load((int) $id);
			if ($model->getId()) {
				$data = Mage::getSingleton('adminhtml/session')->getFormData(true);
				if ($data) {
					$model->setData($data)->setId($id);
				}
			} else {
				Mage::getSingleton('adminhtml/session')->addError(Mage::helper('awesome')->__('Example does not exist'));
				$this->_redirect('*/*/');
			}
		}
		Mage::register('example_data', $model);

		$this->loadLayout();
		$this->getLayout()->getBlock('head')->setCanLoadExtJs(true);
		$this->renderLayout();
	}

	public function saveAction()
	{
		if ($data = $this->getRequest()->getPost())
		{
			$model = Mage::getModel('awesome/example');
			$id = $this->getRequest()->getParam('id');
			if ($id) {
				$model->load($id);
			}
			$model->setData($data);

			Mage::getSingleton('adminhtml/session')->setFormData($data);
			try {
				if ($id) {
					$model->setId($id);
				}
				$model->save();

				if (!$model->getId()) {
					Mage::throwException(Mage::helper('awesome')->__('Error saving example'));
				}

				Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('awesome')->__('Example was successfully saved.'));
				Mage::getSingleton('adminhtml/session')->setFormData(false);

				// The following line decides if it is a "save" or "save and continue"
				if ($this->getRequest()->getParam('back')) {
					$this->_redirect('*/*/edit', array('id' => $model->getId()));
				} else {
					$this->_redirect('*/*/');
				}

			} catch (Exception $e) {
				Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
				if ($model && $model->getId()) {
					$this->_redirect('*/*/edit', array('id' => $model->getId()));
				} else {
					$this->_redirect('*/*/');
				}
			}

			return;
		}
		Mage::getSingleton('adminhtml/session')->addError(Mage::helper('awesome')->__('No data found to save'));
		$this->_redirect('*/*/');
	}

	public function deleteAction()
	{
		if ($id = $this->getRequest()->getParam('id')) {
			try {
				$model = Mage::getModel('awesome/example');
				$model->setId($id);
				$model->delete();
				Mage::getSingleton('adminhtml/session')->addSuccess(Mage::helper('awesome')->__('The example has been deleted.'));
				$this->_redirect('*/*/');
				return;
			}
			catch (Exception $e) {
				Mage::getSingleton('adminhtml/session')->addError($e->getMessage());
				$this->_redirect('*/*/edit', array('id' => $this->getRequest()->getParam('id')));
				return;
			}
		}
		Mage::getSingleton('adminhtml/session')->addError(Mage::helper('adminhtml')->__('Unable to find the example to delete.'));
		$this->_redirect('*/*/');
	}

}

The Grid Block
Here is where it starts getting a touch tricky. When you originally click on the menu item to see the grid of examples, you are going to the "indexAction" in the controller and simply loading and rendering the layout. This means that you will probably have something halfway useful to see in the awesome.xml layout file. We see that there is only one block defined for that handle, and that block is: 'awesome/adminhtml_example'. This block extends Mage_Adminhtml_Block_Widget_Grid_Container which which tells us that our block (Super_Awesome_Block_Adminhtml_Example) will be be a container for a grid. What does that mean? This container will provide a few buttons at the top and automagically define the grid as a child block of itself. Below I will show you the entire contents of our container, and the piece that builds the name of the grid block (which is in the parent Mage_Adminhtml_Block_Widget_Grid_Container.

Super_Awesome_Block_Adminhtml_Example:

<?php

class Super_Awesome_Block_Adminhtml_Example extends Mage_Adminhtml_Block_Widget_Grid_Container
{
    protected $_addButtonLabel = 'Add New Example';

    public function __construct()
    {
        parent::__construct();
        $this->_controller = 'adminhtml_example';
        $this->_blockGroup = 'awesome';
        $this->_headerText = Mage::helper('awesome')->__('Examples');
    }
}
 protected function _prepareLayout()
    {
        $this->setChild( 'grid',
            $this->getLayout()->createBlock( $this->_blockGroup.'/' . $this->_controller . '_grid',
            $this->_controller . '.grid')->setSaveParametersInSession(true) );
        return parent::_prepareLayout();
    }

Now that we have the container we need to build our grid (Super_Awesome_Block_Adminhtml_Example_Grid):

<?php

class Super_Awesome_Block_Adminhtml_Example_Grid extends Mage_Adminhtml_Block_Widget_Grid
{
    public function __construct()
    {
        parent::__construct();
        $this->setId('example_grid');
        $this->setDefaultSort('id');
        $this->setDefaultDir('desc');
        $this->setSaveParametersInSession(true);
    }

    protected function _prepareCollection()
    {
        $collection = Mage::getModel('awesome/example')->getCollection();
        $this->setCollection($collection);
        return parent::_prepareCollection();
    }

    protected function _prepareColumns()
    {
        $this->addColumn('id', array(
            'header'    => Mage::helper('awesome')->__('ID'),
            'align'     =>'right',
            'width'     => '50px',
            'index'     => 'id',
        ));

        $this->addColumn('name', array(
            'header'    => Mage::helper('awesome')->__('Name'),
            'align'     =>'left',
            'index'     => 'name',
        ));

        $this->addColumn('description', array(
            'header'    => Mage::helper('awesome')->__('Description'),
            'align'     =>'left',
            'index'     => 'description',
        ));

        $this->addColumn('other', array(
            'header'    => Mage::helper('awesome')->__('Other'),
            'align'     => 'left',
            'index'     => 'other',
        ));

        return parent::_prepareColumns();
    }

    public function getRowUrl($row)
    {
        return $this->getUrl('*/*/edit', array('id' => $row->getId()));
    }
}

The _prepareCollection() function gets the collection of data that will populate our grid, and the _prepareColumns() function maps that data into the specific columns. Keep in mind that the _prepareCollection() and the _prepareColumns() can be much more detailed/complicated than my example here, so don't be afraid to try crazy things.

If you stop here, you should have a working grid.

The Forms
If you notice, the getRowUrl() on the grid returns back a url that maps to the editAction() in our controller. That is where were start the "form fun". The editAction() handles both the "edit" scenario and the "new" scenario for the model. It makes no difference to us since it is the same form for both.

The edit action maps to a handle in the awesome.xml layout file which simply defines the block: awesome/adminhtml_example_edit. If we take a look at that block we will see the following code:

<?php

class Super_Awesome_Block_Adminhtml_Example_Edit extends Mage_Adminhtml_Block_Widget_Form_Container
{
    public function __construct()
    {
        parent::__construct();

        $this->_objectId = 'id';
        $this->_blockGroup = 'awesome';
        $this->_controller = 'adminhtml_example';
        $this->_mode = 'edit';

        $this->_addButton('save_and_continue', array(
                  'label' => Mage::helper('adminhtml')->__('Save And Continue Edit'),
                  'onclick' => 'saveAndContinueEdit()',
                  'class' => 'save',
        ), -100);
        $this->_updateButton('save', 'label', Mage::helper('awesome')->__('Save Example'));

        $this->_formScripts[] = "
			function toggleEditor() {
				if (tinyMCE.getInstanceById('form_content') == null) {
					tinyMCE.execCommand('mceAddControl', false, 'edit_form');
				} else {
					tinyMCE.execCommand('mceRemoveControl', false, 'edit_form');
				}
			}

			function saveAndContinueEdit(){
				editForm.submit($('edit_form').action+'back/edit/');
			}
		";
    }

    public function getHeaderText()
    {
        if (Mage::registry('example_data') && Mage::registry('example_data')->getId())
        {
            return Mage::helper('awesome')->__('Edit Example "%s"', $this->htmlEscape(Mage::registry('example_data')->getName()));
        } else {
            return Mage::helper('awesome')->__('New Example');
        }
    }

}

Just like the grid had a container, so does the form. We are just changing some labels on buttons and creating some javascript to handle the save scenarios. Below is the snippet of code in the parent container that builds the name of the block that will be rendered containing the actual form:

protected function _prepareLayout()
    {
        if ($this->_blockGroup && $this->_controller && $this->_mode) {
            $this->setChild('form', $this->getLayout()->createBlock($this->_blockGroup . '/' . $this->_controller . '_' . $this->_mode . '_form'));
        }
        return parent::_prepareLayout();
    }

Next/finally, we look at the actual form class; It's so awesome, you might faint when you see it:

<?php

class Super_Awesome_Block_Adminhtml_Example_Edit_Form extends Mage_Adminhtml_Block_Widget_Form
{
    protected function _prepareForm()
    {
        if (Mage::getSingleton('adminhtml/session')->getExampleData())
        {
            $data = Mage::getSingleton('adminhtml/session')->getExamplelData();
            Mage::getSingleton('adminhtml/session')->getExampleData(null);
        }
        elseif (Mage::registry('example_data'))
        {
            $data = Mage::registry('example_data')->getData();
        }
        else
        {
            $data = array();
        }

        $form = new Varien_Data_Form(array(
                'id' => 'edit_form',
                'action' => $this->getUrl('*/*/save', array('id' => $this->getRequest()->getParam('id'))),
                'method' => 'post',
                'enctype' => 'multipart/form-data',
        ));

        $form->setUseContainer(true);

        $this->setForm($form);

        $fieldset = $form->addFieldset('example_form', array(
             'legend' =>Mage::helper('awesome')->__('Example Information')
        ));

        $fieldset->addField('name', 'text', array(
             'label'     => Mage::helper('awesome')->__('Name'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'name',
             'note'		=> Mage::helper('awesome')->__('The name of the example.'),
        ));

        $fieldset->addField('description', 'text', array(
             'label'     => Mage::helper('awesome')->__('Description'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'description',
        ));

        $fieldset->addField('other', 'text', array(
             'label'     => Mage::helper('awesome')->__('Other'),
             'class'     => 'required-entry',
             'required'  => true,
             'name'      => 'other',
        ));

        $form->setValues($data);

        return parent::_prepareForm();
    }
}

Did you faint?

The only thing not straight-forward here is the line: $form->setUseContainer(true);. This line is important because it is the line that actually causes the form renderer to output the surrounding <form> tags.

Conclusion
There ya have it. We now have a grid/form that allows us to manage the data in the super_awesome_example table. Hope this is helpful!

Tagged as: 33 Comments
22Feb/1139

Adding a New Admin Menu Item

Posted by Ben Robie

If you create a new extension for Magento, chances are that you will need something in the Admin Panel to manage your data. Sometimes, this can be managed from the System > Configuration section, but other times, and new section is needed. In this short post we will give an example of what is needed to create a new menu item in the top navigation in the Admin Panel.

The module that we are working in is the Super_Awesome module. In our local code pool we have a directory structure like:

Super
  |_ Awesome
        |_etc
        |  |_ adminhtml.xml
        |  |_ config.xml
        |_Helper
            |_ Data.php

If you didn't already know, all of the .xml files in the etc directory get jammed together into one big Config object (notice both files begin with the <config> element), so you can accomplish the same thing in one .xml file or many. The standard is to create many to better separate what is being configured.

adminhtml.xml contents:

<?xml version="1.0"?>
<config>
    <menu>
        <awesome translate="title" module="awesome">
            <title>Awesome</title>
            <sort_order>15</sort_order>
            <children>
                <example translate="title" module="awesome">
                    <title>Example</title>
                    <sort_order>1</sort_order>
                    <action>adminhtml/example/index</action>
                </example>
            </children>
        </awesome>
    </menu>
</config>

This configuration will create an upper level menu item called "Awesome" and when you hover over it, it will show the child menu item called "Awesome". The <action> tag indicates that if you click on that menu item, it will make a request to the given url. Although the "Example" menu item has an action, if you click on it, it will fail because there is no controller in this example :) .

config.xml contents:

<?xml version="1.0"?>
<config>
    <modules>
        <Super_Awesome>
            <version>0.1.0</version>
        </Super_Awesome>
    </modules>
    <adminhtml>
        <!-- The <layout> updates allow us to define our block layouts in a separate file so are aren't messin' with the Magento layout files.  -->
    	<layout>
			<updates>
				<awesome>
					<file>awesome.xml</file>
				</awesome>
			</updates>
		</layout>
		<!-- The <acl> section is for access control. Here we define the pieces where access can be controlled within a role. -->
		<acl>
			<resources>
				<admin>
					<children>
						<awesome>
							<title>Awesome Menu Item</title>
							<children>
								<example translate="title" module="awesome">
									<title>Example Menu Item</title>
								</example>
							</children>
						</awesome>
					</children>
				</admin>
			</resources>
		</acl>
	</adminhtml>
    <global>
        <helpers>
            <awesome>
                <class>Super_Awesome_Helper</class>
            </awesome>
        </helpers>
    </global>
</config>

Data.php contents:

<?php

class Super_Awesome_Helper_Data extends Mage_Core_Helper_Abstract
{

}

Why an empty class? Well, in some of the xml elements we have defined: translate="title" module="awesome". This tells whatever is reading the config to use the default Helper in the "awesome" module to do its translations of the titles, etc. If you didn't have this, you would see an error like:

Fatal error: Class 'Super_Awesome_Helper_Data' not found in .../magento-1.5.0.1/app/Mage.php on line 523.

That's it! Clear Cache, Logout, Login and your menu item should appear at the top!

Tagged as: 39 Comments
21Feb/1116

Creating a New Magento Module

Posted by Ben Robie

Magento is a built on top of the Zend Framework. The Zend Framework supports modularizing your application, and Magento went the way of the module as part of it's core design. In this short article, I will explain how to create a new module manually, and how to use the Module Creator to help you create modules quickly.

To create a "bare bones" module in Magento you should create your folder in the local code pool. There are generally two parts to naming the module. 1.) The namespace 2.) The module name.

The namespace is a folder used to group modules together, generally representing the company that developed the module. In this example, the namespace is "Super". The name of our module is "Awesome". So, I will create the following folder structure: base_magento_dir/app/code/local/Super/Awesome.

Next I will do the only real required part of a module; create the module's xml file. If you look in the base_magento_dir/app/etc/modules directory, you will find all of the .xml files that define the Magento core modules. We will create an .xml file in here for our module called: Super_Awesome.xml:

<?xml version="1.0"?>
<config>
    <modules>
        <Super_Awesome>
            <active>true</active>
            <codePool>local</codePool>
        </Super_Awesome>
    </modules>
</config>

Additionally, if we know that our module depends on other modules to function, we can declare those dependencies. In our example, Super_Awesome depends on Mage_Core and Mage_Catalog:

<?xml version="1.0"?>
<config>
    <modules>
        <Super_Awesome>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Core/>
                <Mage_Catalog/>
            </depends>
        </Super_Awesome>
    </modules>
</config>

After you dump your cache, you now have an installed module that does absolutely NOTHING :) . Generally, you will have a folder structure that looks something like this:

Super
  |_ Awesome
        |_Block
        |_controllers
        |_etc
        |_Helper
        |_Model
        |_sql

The folders are pretty self explanatory minus the etc. The "etc" directory generally contains config files; the most common being config.xml. Below is a typical sample of the beginnings of a config file.

<?xml version="1.0"?>
<config>
    <modules>
        <Super_Awesome>
            <version>0.1.0</version>
        </Super_Awesome>
    </modules>
    <frontend>
        <routers>
	       <awesome>
                <use>standard</use>
                <args>
                    <module>Super_Awesome</module>
                    <frontName>awesome</frontName>
                </args>
            </awesome>
        </routers>
        <layout>
            <updates>
                <awesome>
                    <file>awesome.xml</file>
                </awesome>
            </updates>
        </layout>
    </frontend>
    <global>
        <models>
            <awesome>
                <class>Super_Awesome_Model</class>
                <resourceModel>awesome_mysql4</resourceModel>
            </awesome>
            <awesome_mysql4>
                <class>Super_Awesome_Model_Mysql4</class>
            </awesome_mysql4>
       </models>

        <resources>
            <awesome_setup>
                <setup>
                    <module>Super_Awesome</module>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </awesome_setup>
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>

        <blocks>
            <awesome>
                <class>Super_Awesome_Block</class>
            </awesome>
        </blocks>

        <helpers>
            <awesome>
                <class>Super_Awesome_Helper</class>
            </awesome>
        </helpers>
    </global>
</config>

Now you are on your way to making your first billion dollar extension! Woo-hoo!

The Module Creator is a Magento "Extension" that allows you to fill in a few questions (Module Name, Namespace, Magento Root Dir, and some Design questions), and then will auto-generate all the files necessary to get you started on your module. The coolest thing about the Module Creator is the way you can create templates/skeletons/frameworks of modules and store them for later use. For instance, if you are in the business of creating Payment modules, you can create a skeleton with all of the common classes and configs, and then use that skeleton to jump start all of your Payment module development. Module Creator is generally a time saver and recommended for a noobs.

21Feb/112

Rewriting a Block Class

Posted by Ben Robie

As stated in the blog post about rewriting models, the ability to rewrite classes are a super valuable tool when you want to extend the business logic of Magento, or make tweaks to the core.

Again, I will state that you SHOULD NEVER CHANGE THE CORE, and if you can help it, you shouldn't just copy whole classes down into the local pool. If you want a more extensive lesson on rewriting, read the Rewriting a Model Class post. Below we will show you what you need to do to rewrite a block.

Let's say that you LOVE the feature that allows friends to send their other friends emails about products (does anyone do this?). You love it soooo much that you never want anyone to be stopped from doing so. You notice in the block Mage_Sendfriend_Block_Send the function...

   /**
     * Check if user is allowed to send
     *
     * @return boolean
     */
    public function canSend()
    {
        return !$this->_getSendfriendModel()->isExceedLimit();
    }

...and you think to yourself, "There should be NO limit to sending emails!". Here is a time where you would rewrite the block. You have your own "Send" block that you want to use everywhere the Mage_Sendfriend_Block_Send is. Your "Send" block is called Super_Awesome_Block_Example_Send. A simple addition to the config.xml (preferrably in the Super_Awesome module and you are done!

<global>
       <blocks>
           <sendfriend>
               <rewrite>
                   <send>Super_Awesome_Model_Example_Send</send>
               </rewrite>
           </sendfriend>
       </blocks>
 </global>

Don't forget to clear the cache!

Now when your layout files say <block type="sendfriend/send" />, or you create a block using $this->getLayout()->createBlock('sendfriend/send', 'sendy'), your Magento application will use the Super_Awesome_Model_Example_Send block. WOOT!

Tagged as: , 2 Comments
21Feb/119

Rewriting a Model Class

Posted by Ben Robie

One of the best ways to extend the capabilities of the Magneto core is to "rewrite" their classes. If create a new instance of a model class the way you are supposed to (Mage::getModel('awesome/example)), Magento has built in a handy way of being able to "rewrite" your request from model A to model B.

Let's say that you have a business requirement to always show the phrase "loves Magento" after you display the customer name. To do this, you can find every place that outputs the customer name, OR change the way the customer name is gotten. For this example, we will do the latter.

First we need to create our new class:


class Super_Awesome_Model_Customer extends Mage_Customer_Model_Customer
{

    public function getName()
    {
    	$name = parent::getName();
        $name = $name . ' loves Magento';
        return $name;
    }

}

Next, we need a way to tell Magento to use OUR class instead of the one it normally does. So, all over the place in Magento, you will find: Mage::getModel('customer/customer'). This will use the config.xml in the Mage_Customer module to build the class name Mage_Customer_Model_Customer. To get it to use ours instead we will put a rewrite into the config.xml. You can put this rewrite into ANY config.xml, but we will put it in the Super_Awesome module.

 <global>
        <models>
            <customer>
            	<rewrite>
            		<customer>Super_Awesome_Model_Customer</customer>
            	</rewrite>
            </customer>
        </models>
  </global>
Mage::getModel('customer/customer')
                  |           |_ Is the <customer> tag inside the <rewrite> element.
                  |_ Is the <customer> tag outside the <rewrite> element.

So now when you make a call for Mage::getModel('customer/customer'), Magento will FIRST check if there is a rewrite for that class alias and will return an instance of the Super_Awesome_Model_Customer model instead of Mage_Customer_Model_Customer.

If you were trying to rewrite Mage_Customer_Model_Customer_Api to Super_Awesome_Model_Example_Of_Customer_Api, you would add the rewrite:

 <global>
        <models>
            <customer>
            	<rewrite>
            		<customer_api>Super_Awesome_Model_Example_Of_Customer_Api</customer_api>
            	</rewrite>
            </customer>
        </models>
  </global>

If you want to take a look at how Magento checks for rewrites, take a look in Mage_Core_Model_Config->getGroupedClassName():

        $config = $this->_xml->global->{$groupType.'s'}->{$group};

        if (isset($config->rewrite->$class)) {
            $className = (string)$config->rewrite->$class;
        } else {
            if (!empty($config)) {
                $className = $config->getClassName();
            }
            if (empty($className)) {
                $className = 'mage_'.$group.'_'.$groupType;
            }
            if (!empty($class)) {
                $className .= '_'.$class;
            }
            $className = uc_words($className);
        }

        $this->_classNameCache[$groupRootNode][$group][$class] = $className;
        return $className;

Important Note: You cannot rewrite a model class unless it is created by using the Mage::getModel(). So if you have a class like Mage_Customer_Model_Address that extends Mage_Customer_Model_Address_Abstract and you want to rewrite the Mage_Customer_Model_Address_Abstract class to do something different, you are out of luck on the rewrite option. You would need to copy Mage_Customer_Model_Address_Abstract down into your local code pool.

Tagged as: , 9 Comments
20Feb/112

Customizing the Admin Theme

Posted by Ben Robie

Just as you want to customize the frontend in Magento, there are times when you need/want to tweek the admin panel screens to suit your business needs. Most people would probably just change the files under the adminhtml/default/default folder, but this is no bueno when doing upgrades. The easiest way to manage your adminhtml changes is by overriding the adminhtml config with a different/local one.

The simplest way to do that is to jaunt over to the inchoo.net blog and take a look at the post Custom admin theme in Magento. Here they offer a simple, yet very effective module for extending/overriding the adminhtml theme.

Have at it!

Tagged as: , 2 Comments
20Feb/115

Altering the Database Via Setup Scripts

Posted by Ben Robie

A very difficult part of maintaining an application is managing the database changes. Some of the more modern frameworks offer a way to automatically handle schema and data changes within the application. Magento is one of these frameworks. Many developers are used to adding columns or rows to tables and then 2 weeks later forgetting what they did. I know this has bitten me before and I'm glad I don't have to worry about it with Magento.

To start, let me say, "ANY DATABASE CHANGE YOU MAKE SHOULD BE SCRIPTED; THIS INCLUDES SCHEMA AND/OR DATA CHANGES". Hopefully you could hear me when I said that. I put it in CAPS just in case you were hard of hearing.

So the first example is that you need a new table and would like that table populated with some sample data. When you first install the module into your Magento instance. There are some config.xml configurations that need to be configured, as well as the actual script to create the tables.

More than likely, you will have already created the modules section in the config.xml and given your module a version. For some reason, the default is usually 0.1.0 in our modules.

<modules>
        <Super_Awesome>
            <version>0.1.0</version>
        </Super_Awesome>
 </modules>

Next, in the config.xml we need to define the resources. The one we really care about here is the "setup" resource. Remember the name "awesome_setup" and notice the module element inside of it. The module is the "driver" for the setup scripts. If the module's version number changes, it will look for scripts to run.

<global>
        ...
        <resources>
            <awesome_setup>
                <setup>
                    <module>Super_Awesome</module>
                </setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </awesome_setup>
            <awesome_write>
                <connection>
                    <use>core_write</use>
                </connection>
            </awesome_write>
            <awesome_read>
                <connection>
                    <use>core_read</use>
                </connection>
            </awesome_read>
        </resources>
</global>

Now, in our example, we have a module at version 0.1.0, and a resource for "awesome_setup" for the Super_Awesome module.

I'm going to stop right here to explain how/when the setup scripts are run:
The setup script mechanism is run the FIRST time a request is made after the cache is cleared.

  • Magento will get a collection of all the "setup resources" and their corresponding Module version numbers from the config.xml files.
  • Magento will then go to the core_resource table and get a collection of all the "setup resources" and their version numbers stored in the table.
  • Magento will then do 1 of 3 things:
    1. If the setup resource doesn't exist in the core_resource table it will run an install script.
    2. If the resource version in the database is less than the one in the config.xml file, it will run one to many update scripts.
    3. If the resource version in the database is greater than the one in the config.xml file, it will run a rollback script.

Here is the code in the Mage_Core_Model_Resource_Setup class that figures out which script to run:

 /**
     * Apply module resource install, upgrade and data scripts
     */
    public function applyUpdates()
    {
        $dbVer = $this->_getResource()->getDbVersion($this->_resourceName);
        $configVer = (string)$this->_moduleConfig->version;
        // Module is installed
        if ($dbVer!==false) {
             $status = version_compare($configVer, $dbVer);
             switch ($status) {
                case self::VERSION_COMPARE_LOWER:
                    $this->_rollbackResourceDb($configVer, $dbVer);
                    break;
                case self::VERSION_COMPARE_GREATER:
                    $this->_upgradeResourceDb($dbVer, $configVer);
                    break;
                default:
                    return true;
                    break;
             }
        } elseif ($configVer) {
            $this->_installResourceDb($configVer);
        }
    }

Because in our example, we have not yet run any setup scripts and the resource does not exist in the core_resource table, we have created a file called local/Super/Awesome/sql/awesome_setup/mysql4-install-0.1.0.php:

<?php

$installer = $this;

$installer->startSetup();

$installer->run("

-- DROP TABLE IF EXISTS {$this->getTable('super_awesome_example')};
CREATE TABLE {$this->getTable('super_awesome_example')} (
  `id` int(11) unsigned NOT NULL auto_increment,
  `name` varchar(100) NOT NULL,
  `description` varchar(100) NOT NULL,
  `other` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 1', 'Example One Description', 'This first example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 2', 'Example Two Description', 'This second example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 3', 'Example Three Description', 'This third example is reall awesome.');
INSERT INTO {$this->getTable('super_awesome_example')} (name, description, other) values ('Example 4', 'Example Four Description', 'This fourth example is reall awesome.');

");

$installer->endSetup();

Now, if we clear cache and make a request to the application, Magento will run the install script and update the core_resource table to version 0.1.0 for the awesome_setup resource, so next time the versions will be the same and no scripts will be run.

Now, what if you need to clear out the sample data you put in there in the next version of the module?

First, change the module's version number in the config.xml:

<modules>
        <Super_Awesome>
            <version>0.1.1</version>
        </Super_Awesome>
 </modules>

Then create a file called local/Super/Awesome/sql/awesome_setup/mysql4-upgrade-0.1.0-0.1.1.php:

<?php

$installer = $this;

$installer->startSetup();

$installer->run("

DELETE FROM {$this->getTable('super_awesome_example')};

");

$installer->endSetup();

When you clear the cache and make a request from Magento, it will see that you are at version 0.1.0 in the core_resource table, but 0.1.1 in the module. So it runs the upgrade script from 0.1.0 to 0.1.1.

Some things to note:

  • You can put anything into the install scripts. You can run SQL or any kind of php code. Therefore, you can use the setup scripts to move data around; not just make schema changes.
  • There are other ways to manipulate databases rather than using SQL: http://stackoverflow.com/questions/4315660/alter-table-in-magento-setup-script-without-using-sql
  • If you have a high traffic site, there is a possibility that the setup scripts will be run multiple times. For instance, if you clear the cache and Request 1 comes in and kicks off the setup script (that for example's purposes takes 5 seconds), and a second later Request 2 comes in, it will see that the core_resource is still at the old version number and try to run the setup scripts again. Magento's official recommendation is to shut down the frontend when you release a new module version and run the scripts manually by clearing cache and hitting the backend.

Alrighty, go change your database schema now, and do it through scripts!

Filed under: Database, Magento 5 Comments