10 useful ways to harness correctly the possibilities of Magento

Many times we get such problematic Magento projects that we need to improve and make fully functional in a relatively short period of time. Our priority is not to leave the project to its fate, but to better the quality of the code so no refactoring of the code is needed. That is why it is crucial to make the most out of the existing possibilities not only for our use, but also for later developments or extensions.



In the following paragraphs I will write about the errors that we may encounter in blocks, layout xmls, installer scripts and template files (.phtml) as well as code parts that still lack some structural or other improvements even though the code has already been delivered. The examples to be shown later are not the only ways for tackling these issues, they represent only one approach to each to solve a given problem. I will detail the following in this article:

  • Array key value pairs in Magento
  • Examining object values in a condition statement (informative values)
  • Using block types in Magento
  • Code repetition in phtml
  • Unavoidable queries (how to handle them)
  • Collection separation at display
  • Exceptions (erroneous runs) of Installer Scripts and how to handle them
  • Interrelated and sub collections
  • Preparation of Module for multi-language use
  • Using Helpers

 

Array key value pairs in Magento

Even if you are sure that an array needs a specific key, this may change over time, so it is better not to have such a defined reference in the code:

$value = $data['key'];

One of the most deeply set parent class of nearly all Magento objects is Varien_Object. Through its magic-methods you can easily check these values. The array values passed over in the constructor of this class can be obtained by calling the $obj->getData(‘key’) method or, staying with this example, calling the $obj->getKey() method, which returns with NULL if the value assigned to the key is missing.

$objData = new Varien_Object($data);
$value = $objData->getKey(); //same as $objData->getData('key');

Then you can enter the required fork with an if(!is_null($value)){…} or with an if(!empty($value)){…} analysis in which the value is not NULL.  

Examining object values in a condition statement (informative value)

If ($objData->getFlag() == 1) {/*...*/}

The value is easily identified any time by the developer, but after a few months or when involving a new developer, it becomes just a number without meaning. In contrast to this, in the example below, it is clear what the informative variable/value and constant values refer to and you also see which module they are linked to.

If ($objData->getFlag() == Module_Helper_Data::FLAG_STATUS_ENABLED) {/*...*/}

It is even better if in such a case you store these elements, statuses in the example, in the Model and in the table that belongs to it. During the assessment/evaluation process you check these by the Module_Helper_Data::getEnabledStatusId() call using the helper. Both solutions are correct, the point is to get the source and meaning of the analysed value.  

How to utilize Block types in Magento

Magento comprises far fewer Block types than html elements, but it is still worth utilizing them. It happens quite often that some code parts that were originally “meant to be present in one place only”, are placed at least in 3-4 locations in the end, and “locked” in a phtml, like this:

<select name="my_select" id="mySelect">
    <?php foreach ($items as $item): ?>
        <option value="<?php echo $item->getId() ?>"><?php echo $item->getName() ?></option>
    <?php endforeach; ?>
</select>

For example, such a Block is Select, to be found at app\code\core\Mage\Core\Block\Html\Select.php – With the help of its methods, a list can be parameterized, which can be displayed in several locations and shows better in the source:

<module_controller_action>
    <reference name="content">
        <block type="aion_module/block_type" name="aion_module_block_type" as="blockalias">
            <block type="core/html_select" name="selectchild" />
        </block>
    </reference>
</module_controller_action>

You also have the possibility to set the parameters within the layout and also through the method call using the reference Block, within the method itself. We are looking at this option now. Create a getSomeSelectHtml method within the Block that you refer to as reference where you prepare Select for rendering/output:

public function getSomeSelectHtml()
{
    $select = $this->getChild('selectchild');
    $select->setData(
        array(
            'id' => 'frontend-id',
            'class' => 'frontend-class-one frontend-class-two',
        )
    );
    //select upload with options $select->setOptions() || $select->addOption()
    return $select->toHtml();
}

when finished, you only need to get the list displayed in the phtml file by calling echo $this->getSomeSelectHtml();  

Code repetition in phtml

Sometimes you need to extend an existing function, which already has a template file. It is recommended to create a new template file, even if there is already an if-else, a switch-case or another template file.

Switch ($mode):
    Case 'list':
        Foreach ($entities as $entity): ?>
            <div class="dsadsa"><a href="..."></a><!--...--></div>
        <?php Endforeach;
    Break;
    Case 'grid':
        Foreach ($entities as $entity): ?>
            <div class="dsadsa"><a href="..."></a><!--...--></div>
        <?php Endforeach;
    Break;
Endswitch;

These codes should be put in a higher-level/parent Block where you call the appropriate type based on the “if” or “switch” element. It is a simpler method because many times the same html elements are present at these forks, containing additional forks. Organizing these into lower-level Blocks, you get a nice structure where a display modification can be done in one location, but affects every related part of the page. Additionally, it is also useful for front-end developers and the code is clearer this way too.

<block type="something/list" name="something.list">
    <block type="list/grid" name="gridList">
        <block type="list/item" name="gridListItem"/>
    </block>
    <block type="list/row" name="rowList">
        <block type="list/item" name="rowListItem"/>
    </block>
</block>
If ($mode == Module_Helper_Data::MODE_LIST) {
    $block->getChildHtml('rowList'); //amin belül még az item-et is getChildHtml()-el kérjük le
} else if ($mode == Module_Helper_Data::MODE_GRID) {
    $block->getChildHtml('gridList');
}

 

Unavoidable queries (how to handle them)

In some non-ideal cases, it may happen that due to performance or other issues, under heavy pressure, you must run direct (not collection related) database queries. This is a “necessary crime” that has its own required form. Magento provides the possibility to modify an existing Collection Select and also to create a new, custom query. In both cases, the class to be returned will be Varien_Db_Select by default, which you can continue working on with further method calls.

$results = $conn->fetchAll("SELECT *, DATE_ADD( tn.`created_at` , INTERVAL tn.`duration_days` * 0.9 * 24 * 60 MINUTE ) as end_date FROM `tablename` as tn INNER JOIN `anothertablename` as atn ON (tn.atn_id = atn.id) WHERE tn.`somecolumn` = 1 GROUP BY atn.`groupcolumn`");

The above example is a very short and simple query, still it is not really clear and thus needs more time to comprehend for a developer who sees it for the first time. So let’s change its form:

//in the installer you can get the connection object like this $this>getConnection();
$conn = $collection->getConnection();
$select = $conn->select()
    ->from(
        array('tn' => 'tablename'),
        array(
            '*',
            'end_date' => new Zend_Db_Expr('DATE_ADD( tn.`created_at` , INTERVAL tn.`duration_days` * 0.9 * 24 * 60 MINUTE )')
        )
    )->joinInner(
        array('atn' => 'anothertablename'),'tn.atn_id = atn.id','*'
    )->where(
        'tn.`somecolumn` = ?',1
    )->group('atn.`groupcolumn`');

It is now much clearer. Now you have Select, it is time to call the necessary records. The simplest way to do that is $results = $conn->fetchAll($select); which only needs to be iterated.  

Collection separation at display

There can be elements with different statuses in a single table. This is nothing new, and it is also known that there are cases where either A-status elements or B-status elements are displayed only on a page. However, these should be filtered on a collection basis, and not in a cycle in the Block or in the template file.

$entities = Mage::getResourceModel('module/some_collection');
$typesA = array();
$typesB = array();
foreach ($entities as $entity):
    if ($entity->getStatus() == 1 || $entity->getStatus() == 2):
        $typesA[] = $entity;
    else:
        $typesB[] = $entity;
    endif;
endforeach;

If you divide a collection in a cycle into different parts, the result (allocated to different pages) of the things that you display on a page, can be a first page where you tell the user that the B-status element is not available, and then there is a 2nd or 3rd page where you see N number of B-status elements. Additionally, the limit is calculated for the whole collection and not for statuses, so the distribution of the elements within the groups will not be even.

//filtered for status, both collections limited to 10
$A = $this->getItemsToStatusA(10); 
$B = $this->getItemsToStatusB(10); 

foreach ($A as $item) {/*...*/}
foreach ($B as $item) {/*...*/}

 

Exceptions (erroneous runs) of Installer Scripts and how to handle them

In the case of installers, you should always prepare the script for handling errors that may arise. There can only be a version upgrade if everything is solved which is defined in the present version. This is an important point because sometimes it is not possible with all the installers to check every single attribute or element that were added by the previous installer. And it is not your task to check the work of others.

$installer->startSetup();
try{
    //do what you have to do
} catch(Exception $e){
    Mage::logException($e);
}
$installer->endSetup(); //this method will upgrade the version number

In my opinion, the simplest way to inspect the run of the installer script presented above is to check the version number of the module. This naturally entails that the script upgrades the version of the module only after a totally successful run.

if(Mage::getConfig()->getModuleConfig("Package_MyModule")->version == '1.5.3'){
    $installer->startSetup();
    try{
        //do what you have to do
        $installer->endSetup(); //this method will upgrade the version number
    } catch(Exception $e){
        Mage::logException($e);
    }
}

This method, namely letting the present version be upgraded only if there has been a flawless run, also helps you avoid upgrading the version unnecessarily if our installer has faults. Consequently, other installers, e.g. data installers, which do not inspect if a given cms/page or cms/block exists already or not, are not run repeatedly.  

Interrelated and sub collections

In the case of sub collection (parent-child) relations, the database queries inside the cycle are much slower. If possible, try to use the resources with fewer queries and more targeted filtering.

$mainCollection = Mage::getModel('module/main')->getCollection()
    ->addFieldToFilter('field', array('in', array('value1', 'value2', 'valueN')));

foreach ($mainCollection as $mainCollectionItem) {
    $subCollection = Mage::getModel('module/sub')->getCollection()
        ->addFieldToFilter('parent', $mainCollectionItem->getSomeField());
    foreach ($subCollection as $subItem) {
        /*...*/
    }
    $subCollection->save();
}

The above example is more demanding in terms of time and performance, it is better to call the collection beforehand and iterating through the items. The collections offer the possibility to return such elements (they already hold) that are attached to a given field. It is also possible to get all the values of the given fields of a collection, with which you can get the second collection and so on, without using a cycle.

$mainCollection = Mage::getResourceModel('module/main_collection')
    ->addFieldToFilter('field', array('in', array('value1', 'value2', 'valueN')));

$subCollection = Mage::getResourceModel('module/sub_collection')
    ->addFieldToFilter('parent', array('in'=>$mainCollection->getColumnValues('some_field')));

foreach ($mainCollection as $mainCollectionItem) {
    $mainSubCollection = $subCollection->getItemsByColumnValue(
        'parent', $mainCollectionItem->getColumnValues('some_field')
    );
    foreach ($mainSubCollection as $subItem) {
        /*...*/
        $subItem->save();
    }
}

As you can see in this example, you should also pay attention to the fact that while in the first case you can save all the item changes with a $subCollection->save(); call at the end, in the second case you cannot call a method since $subCollection->getItemsByColumnValue(‘parent’, …) returns an array. So you need to call it in the cycle with the items one by one. $subItem->save();  

Preparation of Module for multi-language use

There is no need for the module to handle all languages by default, it is enough to have the default language plus English if it is not your default language.

<span>
    <label class="main label" for="next_input">This is the label of the input</label>
        <input type="text" name="next_input" id="next_input" placeholder="Next input" />
</span>

It is just a minor extra effort to prepare your module for displaying texts in other languages, even if they are not yet available.

<span>
    <label class="main label" for="next_input"><?php echo $this->__('This is the label of the input'); ?></label>
    <input type="text" name="next_input" id="next_input" placeholder="<?php echo $this->__('Next input'); ?>" />
</span>

Besides, it is a plus that the texts linked to the module are prepared for additional languages, it is just a little work to make it possible for the users to rewrite the texts in their preferred language, so they can easily find the appropriate foreign expressions in the newly downloaded module.

Using Helpers

Helper calls can be included in Blocks, phtmls, Controllers or Models. The point is to put such data, constant values or methods in the Helper that are used frequently or do not belong to any specific category. Obtaining a config value can be such a call Mage::getStoreConfig(‘module_config_path’); or config flag Mage::getStoreConfigFlag(‘module_config_path’); Defining a value CONSTANT MODULE_ENABLED_STATUS = 2; (can also be placed in a module linked to it), or other minor modules to be used in several places can also be included here.

Summary

All in all, it can be said that if you thoughtfully make code developments, then you will get a fast, stable and clear code in which all other experts can find what they look for. Even if it is a more complex solution, it is still worth investing in in the long run. When the quality of the code is in question, just think about what you would say when you ask yourself the following questions like “What does the code look like at first sight?” or “Have I exploited all the possibilities that I knew about?” Thus you can make sure you will deliver a state-of-the-art code.

 

 

István Dombi

István Dombi

Backend Developer

István is a Senior Magento Developer. He is not afraid of challenges, even if they mean problems that others have already failed to solve. He is not particularly specialized in any given field of Magento, he just likes diving headlong into tasks that need a complex way of thinking. When not sitting in front of the computer screen, he happily spends his free time fishing.


NEED A RELIABLE, PROFESSIONAL MAGENTO DEVELOPMENT PARTNER?

Contact us if you have any question or requirement related to the preparation of a new or renewal of an existing online store.

magento_request
Do you need our support?
  • Magento Site Check
  • Magento Code Audit
  • Magento SEO Audit
  • Magento Project Rescue
Request help
Next