EcomDev_PHPUnit Tip #1

For years, the test framework EcomDev_PHPUnit is quasi-standard for Magento unit tests. The current version is 0.3.7 and last state of official documentation is version 0.2.0 – since then, much has changed which you have to search yourself in code and GitHub issues. This series shall collect practical usage tips.

Tip #1: Reset Global State

Something that makes testing with Magento quite difficult, is the liberal use of global state, in form of singletons and registry. They are not reset between tests, but EcomDev_PHPUnit allows explicit resetting with annotations.

/**
 * @singleton checkout/session
 * @helper tax
 * @registry current_product
 */
public function testSomething()

The parameters are the same as for Mage::getSingleton(), Mage::helper() and Mage::registry().

It is recommended to reset all singletons and registry values that are used during the test, not only after you get conflicts. Especially resetting used session singletons is important, regardless if they are mocked in the current test. Only for stateless helpers, i.e. those that don’t have own attributes, resetting is not necessary.

CSV Processing In Magento

A development principle , not only with Magento, is that you shouldn’t try to reinvent the wheel and especially use the functions of the used framework wherever possible. Magento has many more or less known universal helpers, in the helper classes in Mage_Core as well as in lib/Varien and of course in the Zend Framework.

A classic is for example JSON encoding. Although PHP has its built-in functions json_encode and json_decode, but they have some shortcomings that are compensated for in the Zend_Json implementation. So Zend_Json::encode() has a cycle check, Magento added support for inline translations within JSON strings in Mage_Core_Helper_Data::jsonEncode(). Thus in Magento you always should use Mage::helper('core')->jsonEncode() (and jsonDecode).

Varien_File_Csv

How is it with processing CSV files? Since import and export works with CSV files in the standard implementation, Magento should have somthing, right? Presenting Varien_File_Csv! Well, I’ll anticipate the result: except for very simple tasks with small files, the class is not useful at all.

Continue reading “CSV Processing In Magento”

Visualizing Magento Indexer Progress

Magento Reindex Progress

At the Magento Hackathon Zürich 2014, Dima Janzen from Mash2 and I picked up on a previous item on the project ideas list „Visualize reindexing“ (Credits to Tim Bezhashvyly for proposing that). The team that started implementing it last time told us that there was no sane way to determine the progress of processed events, which also would have been our first approach. So we tried to think out of the box and came up with another approach: Estimating the total running time per indexer based on previous times, something that is also used in other software, like build servers.

Luckily, this data is easy to retrieve, Magento already saves start and end time of the current/latest run in the index_process table, we just needed to persist it in a second table to get a history.

Then the large part of the work, was to build a nice user interface around it. The main goals were

  • Unobtrusive integration in the indexer grid
  • Real time information

Read more at integer-net.de

A JSON-RPC Adapter For The Magento API

Going through my old answers on Magento StackExchange, I found this question about using the Magento API via JavaScript and noticed that the link to GitHub that contained an important part of the solution, namely implementing a JSON-RPC adapter for the Magento-API is dead now.

So I decided to publish my complete module myself (the original link was a core hack, I rewrote it as a clean module):

GitHub: SGH_JsonRpc

The whole module is less than 100 lines of code. In config.xml our controller is added to the api route:

    <frontend>
        <routers>
            <api>
                <args>
                    <modules>
                        <sgh_jsonrpc before="Mage_Api">SGH_JsonRpc_Api</sgh_jsonrpc>
                    </modules>
                </args>
            </api>
        </routers>
    </frontend>

The new API adapter is defined in api.xml:

Continue reading “A JSON-RPC Adapter For The Magento API”

Efficiently Increase/Decrease Magento Attributes

Magento.SE Screenshot

This question arose on Magento StackExchange:

I need to decrement a value with an atomic database operation, is it possible using Magento models?

It is in fact possible, with a lesser known technique using Zend_Db_Expr. I’ll share it here as well:

$object->setNumber(new Zend_Db_Expr('number-1'));

For reference:

The method Mage_Core_Model_Resource_Abstract::_prepareDataForSave() contains the following code:

if ($object->hasData($field)) {
    $fieldValue = $object->getData($field);
    if ($fieldValue instanceof Zend_Db_Expr) {
        $data[$field] = $fieldValue;
    } else {
        ... [normal value processing follows]

EAV Models:

Note that you only can reference the attribute by its name (“number” in the example) if it’s a real column of the main table, not an EAV attribute.

Although the abovementioned method is only used by models with flat tables, Zend_Db_Expr can be used for EAV attributes as well, the method that handles it is Varien_Db_Adapter_Pdo_Mysql::prepareColumnValue().

BUT you always have use the column name “value“:

$product->setNumber(new Zend_Db_Expr('value-1'));

You don’t need to specify a table alias because during save each attribute gets processed with its own query, so “value” is not ambiguous.

Spryker vs. Magento

Recently I had the opportunity to take a peek into the source code of Spryker, the e-commerce framework that’s composing itself to be the new player in the enterprise area. Spryker was originally developed by the Berlin based incubator Project A to be used in the companies that they build up and is supposed to be released to the public this year. While “public” is not enirely true, I can say so much in advance: An open source version is not planned, licenses will cost about 100.000 € / year. Agencies can register as partner, to get access to source code and documentation without a license. With the first sold license they get access to additional training material. Freelancers are not addressed. Already listed as partner are the agencies CommercePlus and Symmetrics that are well-known in the German Magento scene.

What is special about Spryker?

Spryker doesn’t consider itself as ready-to-use product but as framework that provides the building blocks for an individual e-commerce solution from which you can choose and extend for your project. This takes the reality into account that no project is equal to the next and each shop has its own processes and infrastructure, which you have to address individually.

The core of Spryker are two separate applications, Yves and Zed. In short, Yves is a lightweight application for the frontend, Zed the big gun for the backend.

Discover Spryker
Image: http://spryker.com/product/

Yves (in the first version developed with Yii, now with Silex) reads any needed data from an in-memory NoSQL backend such as Redis.
Zed (Zend Framework 2) handles communication with MySQL, message queue and external systems, and contains the business logic for order processes and so on.

Read more at magenticians.com Continue reading “Spryker vs. Magento”

Magento: Direct Link To Tab in Adminhtml Tab Widgets

For an extension I was recently working on, I wondered if there was a built-in way to link directly to a tab on a backend page. My research didn’t result in anything useful (read: my Google foo had failed me), so I dug into the core code to see where to start. I’ll share what I found out.

The Problem

In particular, I wanted to link to the “Manage Label / Options” tab on the “Edit Product Attribute Page”:

Screenshot

The Solution

Actually it’s possible with a URL parameter ?active_tab=$id.

How To Find The Right Tab ID

Find the responsible tab container class. This is a child class of Mage_Adminhtml_Block_Widget_Tabs, in my case, Mage_Adminhtml_Block_Catalog_Product_Attribute_Edit_Tabs.

You’ll find calls to $this->addTab(), usually in the methods _beforeToHtml(), or _construct(). The first parameter of addTab() is the tab id:

    $this->addTab('labels', array(
        'label'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'title'     => Mage::helper('catalog')->__('Manage Label / Options'),
        'content'   => $this->getLayout()->createBlock('adminhtml/catalog_product_attribute_edit_tab_options')->toHtml(),
    ));

So, the URL is /admin/catalog_product_attribute/edit/attribute_id/123/?active_tab=labels, generated with this code (within an adminhtml block):

    $this->getUrl('adminhtml/catalog_product_attribute/edit',
        array('attribute_id' => 123, '_query' => array('active_tab' => 'labels'));

How it works

Let’s have a look at the responsible code:
Continue reading “Magento: Direct Link To Tab in Adminhtml Tab Widgets”

Magento ACL: No More “Log Out And Back In” After Installing Extensions

In almost every Magento extension installation guide you find the step “Log out and log in again” which is necessary to gain access to new sections in the backend menu or system configuration. But why exactly do we put up with it? The problem is that the access control list (ACL) is loaded only on login and then cached in the session. Loading it on every request is no viable alternative because it would slow down the backend too much.

But with just a few lines of code we can make reloading the ACL a little more comfortable.

The Code

This controller action reloads the ACL by request:

class SSE_AclReload_Adminhtml_Permissions_AclReloadController
    extends Mage_Adminhtml_Controller_Action
{
    public function indexAction()
    {
        $session = Mage::getSingleton('admin/session');
        $session->setAcl(Mage::getResourceModel('admin/acl')->loadAcl());

        Mage::getSingleton('adminhtml/session')->addSuccess(
            $this->__('ACL reloaded'));
        $this->_redirectReferer();
    }
}

This template provides a button that links to the new controller:

<?php
$request = Mage::app()->getRequest();
?>
<a href="<?php echo $this->getUrl('adminhtml/permissions_aclReload/index'); ?>">
<?php echo $this->__('Reload ACL'); ?>
</a>

And this layout update adds the button to error 404 pages in the backend (those that you get when you don’t have access to a page):

    <adminhtml_noroute>
        <reference name="content">
            <block type="adminhtml/template" name="content.aclReload"
                after="content.noRoute" template="sse_aclreload/button.phtml" />
        </reference>
    </adminhtml_noroute>

The Result

Screenshot Magento Admin 404

Continue reading “Magento ACL: No More “Log Out And Back In” After Installing Extensions”

TranslationHints 0.2 For Magento Published

Screenshot: Translation Hint

I released version 0.2 of my Magento extension SSE_TranslationHints, a developer tool that shows the source of translations together with alternative overridden translations for the same string directly in the frontend.

The configuration is still done in the same way as template hints:

Screenshots: Translation Hints Configuration

News

Together with the source of the translation you see alternative translations that have been overridden by the actual source, and some additional data.

In the following example you see the scope of the translation (Mage_Customer), the translation for this scope, as well as the translation that would be used for global scope, i.e. if there was no scope specific translation. The CACHED tag tells us that the translations have been loaded from translation cache:

Screenshot: Translation Hint

Continue reading “TranslationHints 0.2 For Magento Published”

Magento: Lost Block Content After core_block_to_html_after Event

Be careful if you use Mage_Core_Block_Abstract::toHtml() inside an observer for core_block_to_html_after. I did that to inject HTML from another block into an existing block with the result that everything except this new block was removed from output.

Digging through the core, I found the cause in how the event is dispatched:

        /**
         * Use single transport object instance for all blocks
         */
        if (self::$_transportObject === null) {
            self::$_transportObject = new Varien_Object;
        }
        self::$_transportObject->setHtml($html);
        Mage::dispatchEvent('core_block_abstract_to_html_after',
            array('block' => $this, 'transport' => self::$_transportObject));
        $html = self::$_transportObject->getHtml();

As you see, the transport object that is used to allow modification of the HTML in observers, is a class variable of Mage_Core_Block_Abstract, so basically acts like a singleton. I cannot think of a good reason for this, but it smells ridiculously like premature optimization.

Now what’s happening is, that during the event is processed, the new block sets its own output to the same transport object in its toHtml() method and the original output is lost, if you didn’t save it before.

The solution to my particular problem was to generate and pre-render the new block elsewhere but if you don’t, you need to copy the output at the very beginning:

$html = $observer->getTransport()->getHtml(); // should be the first statement

// now you can do what you want, create other blocks, modifiy $html ...

$observer->getTransport()->setHtml($html); // should be the last statement