Comparable Interface For PHP

About 5-6 years ago I had my “PHP should be more like Java” phase and experimented a lot with things like string objects and method overloading, which usually required hackish workarounds and most things did not turn out to be very practical in the long run.

But there is one package I still like very much, namely ComparatorTools, which got to be place 2 in the monthly PHPclasses.org innovation awards after all. It provides Comparable and Comparator interfaces and functions similar to the core array functions, that can work with these.

Interfaces

The interfaces resemble the corresponding Java interfaces, except that we do not have Generics in PHP, so it is cannot be guaranteed that compared objects have the same type. This has to be checked at runtime in the implementation, if needed. An exception type for these cases is provided:

interface Comparable
{
	/**
	 * @param object $object
	 * @return numeric negative value if $this < $object, positive if $this > $object, 0 otherwise (if objects are considered equal)
	 * @throws ComparatorException if objects are not comparable to each other
	 */
	public function compareTo($object);
}
interface Comparator
{
	/**
	 * @param object $object1
	 * @param object $object2
	 * @return numeric Negative value if $object1 < $object2, positive if $object1 > $object2, 0 otherwise
	 * @throws ComparatorException if objects are not comparable to each other
	 */
	public function compare($object1, $object2);
}

Continue reading “Comparable Interface For PHP”

EcomDev_PHPUnit Tip #5

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 #5: Secure Area

Problem: test cases extending EcomDev_PhpUnit_Test_Case_Controller with customer fixture fail with Cannot complete this operation from non-admin area because Magento is in area=frontend mode during tearDown and does not allow deletion of customers. The same problem occurs when you try to delete customers in a test outside of an adminhtml context.

The customer model checks if the isSecureArea flag is set in the Magento registry, so to solve the problem, we set this flag in the test. There are two possible ways to do it:

1.) If you create and delete customers within the test:

/*
 * @test
 * @registry isSecureArea
 */
public function testThatNeedsToDeleteCustomers()
{
    Mage::register('isSecureArea', true);
    // ...
}

(note the @registry annotation which will reset the flag afterwards, see Tip #1)

2.) If you use a customer fixture:

protected function setUp()
{
    Mage::register('isSecureArea', true);
    parent::setUp();
}
protected function tearDown()
{
    parent::tearDown();
    Mage::unregister('isSecureArea');
}

or if you use class wide fixtures:

    public static function setUpBeforeClass()
    {
        Mage::register('isSecureArea', true);
    }
    public static function tearDownAfterClass()
    {
        Mage::unregister('isSecureArea');
    }

EcomDev_PHPUnit Tip #4

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 #4: Named Parameters

The advantage of YAML files for configuration is supposed to be easy readability. But if a data provider looks like this, there’s not so much readability left:

-
  - 7
  - 1
  -
    5: 7
    6: 9
-
  - 7
  - 1
  -
    5: 8
    6: 10

Technically the same, but way more maintainable:

Bundle_X_A1_B2:
  product_id: 7
  qty: 1
  bundle_selections_by_option:
    5: 7
    6: 9
Bundle_X_A2_B2:
  product_id: 7
  qty: 1
  bundle_selections_by_option:
    5: 8
    6: 10

You can at least guess, that this one is about adding bundle products to the cart, and if you know it, the test data is easy to understand, without having to look at the source code.

Another positive effect is, that for failed tests PHPUnit doesn’t show messages like TestCase::test() with data set #1 anymore, but for example TestCase::test() with data set "Bundle_X_A1_B2"

Magento Theme Refactoring

A very common issue in 2014 was „I updated to Magento 1.8 and now my (login|cart) form is not working anymore”. The reason for this happening was that starting with Magento 1.8, the form key was used in more forms as a security feature (it prevents CSRF attacks). But any theme that had its own version of the according templates did not include the form key, so that the server side validation of the form silently failed.

Of course there are themes whose markups are so different from the default theme, that most templates are overridden with good reason. However I see at least as many themes, especially custom themes, where all templates were copied from base/default and then modified. There is almost no excuse to overwrite layout XML files because one can modify the layout in many ways with an additional theme specific local.xml file.

The issue mentioned above is a good example of the reasons for the „pay attention to updatability“ mantra. The errors could have been avoided if only files had been copied, that really required adjustment(s).

With this article I want to explain the process I’m following for theme refactoring (just the structural part, the HTML source will look exactly as before afterwards — except from changes in newer versions of the default templates).

Read more at integer-net.com

EcomDev_PHPUnit Tip #3

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 #3 Shared Data Providers

Did you ever wonder, why you can specify the file name for expectations and fixtures, but for data providers you seem to be limited to the default “test name dot yaml”? The simple reason is, @dataProvider is a native feature of PHPUnit and its parameter actually must be a method name.

So @dataProvider dataProvider means, the method EcomDev_PHPUnit_Test_Case::dataProvider() is used as data provider:

    /**
     * Implements default data provider functionality,
     * returns array data loaded from Yaml file with the same name as test method
     *
     * @param string $testName
     * @return array
     */
    public function dataProvider($testName)
    {
        return TestUtil::dataProvider(get_called_class(), $testName);
    }

But EcomDev_PHPUnit also has a way to specify the file name for the data provider explicitly, thus allows shared providers:

/**
 * @dataProvider dataProvider
 * @dataProviderFile customFileName.yaml
 */
public function testSomething($something)

EcomDev_PHPUnit Tip #2

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 #2: Expectation Keys

This is a short one: expected('works %s sprintf', 'like').

Which expectations should be loaded, usually depends on input data, so if your expectation file looks like this:

- product-1-qty-10:
  - answer: 42
- product-2-qty-10:
  - answer: 42

you can load it in the test like this:

/**
 * @test
 * @loadExpectation
 * @loadFixture
 * @dataProvider dataProvider
 */
public function testSomething($productId, $qty)
{
  $expectedAnswer = $this->expected('product-%s-qty-%s', $productId, $qty);
}

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”