Magento – Inchoo http://inchoo.net Magento Design and Magento Development Professionals - Inchoo Mon, 29 May 2017 08:52:06 +0000 en-US hourly 1 https://wordpress.org/?v=4.7.3 External database connection in Magento http://inchoo.net/magento/magento-database/external-database-connection-magento/ http://inchoo.net/magento/magento-database/external-database-connection-magento/#comments Thu, 27 Apr 2017 14:58:39 +0000 http://inchoo.net/?p=29408 Most of the time working with Magento, a single database connection is just enough. Magento has excellent system of adding new tables in database or extending existing ones. So, why would there be a need for an external database connection outside the Magento system? Well, one of the examples is data migration from another ecommerce...

The post External database connection in Magento appeared first on Inchoo.

]]>
Most of the time working with Magento, a single database connection is just enough. Magento has excellent system of adding new tables in database or extending existing ones. So, why would there be a need for an external database connection outside the Magento system? Well, one of the examples is data migration from another ecommerce system. In this article, a simple connection to external database is explained with CRUD (create, read, update, delete) examples.

Configuration

This external database connection is similarly defined as the Magento default one – in an XML configuration. The difference is that foreign connection is defined inside particular module’s XML configuration. It defines read and write adapters, setup and database credentials information. Foreign tables are defined in the same way as magento tables. They are under inchoo_foreignconnection_resource node so the model resource can be invoked later in the code. For demonstration purpose, there’s a frontend node in XML configuration that defines front name of the controller (fconn).

<?xml version="1.0"?>
<config>
    <modules>
        <Inchoo_ForeignConnection>
            <version>1.4.2</version>
        </Inchoo_ForeignConnection>
    </modules>
    <global>
        <models>
            <inchoo_foreignconnection>
                <class>Inchoo_ForeignConnection_Model</class>
                <resourceModel>inchoo_foreignconnection_resource</resourceModel>
            </inchoo_foreignconnection>
            <inchoo_foreignconnection_resource>
                <class>Inchoo_ForeignConnection_Model_Resource</class>
                <entities>
                    <product>
                        <table>product_description</table>
                    </product>
                </entities>
            </inchoo_foreignconnection_resource>
        </models>
        <resources>
            <inchoo_foreignconnection_write>
                <connection>
                    <use>inchoo_foreignconnection_database</use>
                </connection>
            </inchoo_foreignconnection_write>
            <inchoo_foreignconnection_read>
                <connection>
                    <use>inchoo_foreignconnection_database</use>
                </connection>
            </inchoo_foreignconnection_read>
            <inchoo_foreignconnection_setup>
                <connection>
                    <use>core_setup</use>
                </connection>
            </inchoo_foreignconnection_setup>
            <inchoo_foreignconnection_database>
                <connection>
                    <host><![CDATA[localhost]]></host>
                    <username><![CDATA[username]]></username>
                    <password><![CDATA[password]]></password>
                    <dbname><![CDATA[db_name]]></dbname>
                    <initStatements><![CDATA[SET NAMES utf8]]></initStatements>
                    <model><![CDATA[mysql4]]></model>
                    <type><![CDATA[pdo_mysql]]></type>
                    <pdo_type><![CDATA[]]></pdo_type>
                    <active>1</active>
                </connection>
            </inchoo_foreignconnection_database>
        </resources>
    </global>
    <frontend>
        <routers>
            <inchoo_foreignconnection>
                <use>standard</use>
                <args>
                    <module>Inchoo_ForeignConnection</module>
                    <frontName>fconn</frontName>
                </args>
            </inchoo_foreignconnection>
        </routers>
    </frontend>
</config>

Model

Next thing is a model that will use defined foreign connection to get data or to save data in a foreign database. Here, the model is initialized with the product table from XML configuration, that in this case defines product_description table.

class Inchoo_ForeignConnection_Model_Product extends Mage_Core_Model_Abstract
{
    protected $_eventPrefix = 'inchoo_foreignconnection_product';
    protected $_eventObject = 'product';
 
    protected function _construct()
    {
        $this->_init('inchoo_foreignconnection/product');
    }
}

Model resource class is also defined with the same xml configuration node in _init() function, but with the TABLE_PRIMARY_KEY parameter. In this class, several functions can be created that will work with external data.

First example is createDataInResource function, which inserts data in model’s table. It takes array of parameters that will be inserted.

Second example is a readDataFromResource function that fetches all data from model’s table. Read adapter must be defined first. It is a configuration node from xml that defines read connection. After read adapter definition, Magento database functions can be used (select(), from(), limit(), etc..). When query is constructed completely, it can be executed with read adapter. Data can be retrieved with fetchPairs() or fetchAll() function. fetchAll() is used to get all records returned from mysql.

updateDataInResource and deleteDataFromResource functions take additional $id parameter that defines which record will be updated or deleted.

class Inchoo_ForeignConnection_Model_Resource_Product extends Mage_Core_Model_Resource_Db_Abstract
{
    const TABLE_PRIMARY_KEY = 'product_id';
 
    protected function _construct()
    {
        $this->_init('inchoo_foreignconnection/product', self::TABLE_PRIMARY_KEY);
    }
 
    public function createDataInResource($values = array())
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->insert(
                $this->getMainTable(),
                $values
            );
        } catch (Exception $e) {
            Mage::log('Unable to insert data to external resource. ' . $e->getMessage(), null, null, true);
        }
    }
 
    public function readDataFromResource()
    {
        $data = array();
        $readAdapter = $this->_getReadAdapter();
        $select = $readAdapter->select()
            ->from($this->getMainTable(), '*')
            ->limit(20);
 
        try {
            $data = $readAdapter->fetchAll($select);
        } catch (Exception $e) {
            Mage::log('Unable to fetch data from external resource. ' . $e->getMessage(), null, null, true);
        }
 
        return $data;
    }
 
    public function updateDataInResource($id, $values = array())
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->update(
                $this->getMainTable(),
                $values,
                self::TABLE_PRIMARY_KEY . '=' . $id
            );
        } catch (Exception $e) {
            Mage::log('Unable to update data in external resource. ' . $e->getMessage(), null, null, true);
        }
    }
 
    public function deleteDataFromResource($id)
    {
        $writeAdapter = $this->_getWriteAdapter();
        try {
            $writeAdapter->delete(
                $this->getMainTable(),
                self::TABLE_PRIMARY_KEY . '=' . $id
            );
        } catch (Exception $e) {
            Mage::log('Unable to delete data from external resource. ' . $e->getMessage(), null, null, true);
        }
    }
}
class Inchoo_ForeignConnection_Model_Resource_Product_Collection extends Mage_Core_Model_Resource_Db_Collection_Abstract
{
    public function _construct()
    {
        $this->_init('inchoo_foreignconnection/product');
    }
}

Usage in controller

All these functions are demonstrated in IndexController class but since they are defined in model’s resource class, they can be called in any controller class.

class Inchoo_ForeignConnection_IndexController extends Mage_Core_Controller_Front_Action
{
    public function indexAction()
    {
        // Create
        $foreignProductCreate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductCreate->createDataInResource(
            array(
                'product_name' => 'Product name',
                'product_description' => 'Product description'
            )
        );
 
        // Read
        $foreignProductRead = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $result = $foreignProductRead->readDataFromResource();
        var_dump($result);
 
        // Update
        $foreignProductUpdate = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductUpdate->updateDataInResource(
            3394,
            array(
                'product_name' => 'Product name updated',
                'product_description' => 'Product description updated'
            )
        );
 
        // Delete
        $foreignProductDelete = Mage::getModel('inchoo_foreignconnection/product')->getResource();
        $foreignProductDelete->deleteDataFromResource(3394);
    }
}

In most scenarios, Magento will use different type of external connection to retrieve or send data, but sometimes an external database connection like this will be the best way to go. One of the examples would be when you want to import products from another system to Magento with their xsell or upsell products. In that case, read connection would be used to retrieve product data and write connection would be used to save xsell or upsell product ids in a temporary table so they can be assigned to Magento product when all products from external system are imported. 

The post External database connection in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-database/external-database-connection-magento/feed/ 2
Javascript Bundling in Magento 2 http://inchoo.net/magento-2/javascript-bundling-magento-2/ http://inchoo.net/magento-2/javascript-bundling-magento-2/#comments Fri, 21 Apr 2017 09:32:09 +0000 http://inchoo.net/?p=29344 Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained). For loading modules, we usually use some of popular module loaders such...

The post Javascript Bundling in Magento 2 appeared first on Inchoo.

]]>
Javascript bundling is a technique that groups separate files in order to reduce the number of HTTP requests that are required to load a page. Bundling is commonly used in today’s “module-based” development where some functionalities are basically split into Modules (roughly explained). For loading modules, we usually use some of popular module loaders such as rollup.js or RequireJS (which is Magento’s weapon of choice).

HTTP2 is here so this technique will be probably deprecated in the future. You could still use bundling for reducing the number of requests for a specific page but I don’t think it will be worth of it.
Right before we start with “reverse” optimisation, we can use bundling to help us organise our assets and serve less files to the client which will (should) result in faster website.

One of our services is Technical audit which covers various tests on both Frontend and Backend part of the client’s store. Since i’m Frontend Developer, performance is my key point during analysis.

First thing I check is wether Javascript and CSS files are merged/minified/bundled (hello bundle). When I was Junior Developer, I was so happy/angry when I discovered someone didn’t turn these ON since I was thinking that technical level of Developers who worked on site was low and we can do a lot to improve site… and of course, I could get a “quick win” with turning merging/minifying ON.

Well, in theory that was great. Usually I was right about their expertise level but I was wrong about one thing – they didn’t “forgot” to turn these ON.

Explaining to the client that you would probably have to refactor 80% of the code to change that “simple option” is something I wouldn’t want to go through again.

We were doing Technical audit of the Magento 2 store and when I found out that Javascript and CSS files were not merged/minified/bundled I was in shock and first question that was going through my head was “shall I report this to the client?!”.

Strange question, I know. But it does make sense because if I report it, I’ll have to fix it.

Just kidding, of course this was included in our report and was first thing I changed when we took over the project.
How did it go? Well, do you write articles about stuff that work as expected?

Magento 2 Bundling

I must note that official docs do not give us a lot of information and there are only few discussions online about it.
Therefore, I may be wrong in some of my conclusions so feel free to correct me in comments 🙂

For start, let’s see 2 proper ways to load assets in Magento 2.
In this example, you can see how assets are loaded on two different Magento pages – Homepage and product page.

As you can see on example above, requireJS loaded different assets through different pages while all bundles were loaded regardless of wether they are needed or not.
With RequireJS, we can load specific JS modules on specific pages which means you will load only necessarily assets and reduce number of requests.

(If you wish to know more about how to load Javascript files with RequireJS, you can read this excelent article from my coleague Domagoj Potkoc. )

While RequireJS did help us with reducing number of requests through the page, we still have few JS files being loaded and “few” in Magento 2 means 40-50. We need a way to merge those assets into just few files. We need to create 5 files out of 50.
Files are being loaded asynchronously but still, if we could merge these files into just few, we could improve performance even more.
Bundling comes as a problem solver since it is used to merge modules and it dependencies into single file.
Bundling denies main benefit of using module loaders such as RequireJS since assets aren’t loaded asynchronously. Bundles are included with <script> tag, inside <head> section of the page.

So, why use it?

With bundling we could decide on where to load specific bundle and that’s the best part of it! For example, we want to put all checkout-related stuff into one bundle and load it only on checkout page!

Feeling happy?

Well, Magento folks didn’t implement RequireJS optimizer which is required for ordering and organising module load across different pages. You can exclude some scripts from bundle but you CAN’T decide on bundle content for a specific page.

So, why use it?

With Merging, you can only merge Javascript files that are NOT being loaded through RequireJS. If you wish to “merge” JS modules, you will have to use bundling.

You probably have so many questions in your head right now. So do I. And I’m still searching for the answers.

Here is a random Zen image, just to chill you down.

Bundling misses key features (imho) but you can still change few things in order to organize your bundles.

Bundle size and exclude list

In Vendor/Theme/etc/view.xml you can change bundle size and exclude some scripts from bundle.

Default size for bundle is 1MB.
Bundle size determines number of bundles that will be created. For example, if you have 4MB of script files and bundle size is set to 1MB, you will have 4 bundles created.

If number is too low, you will probably have 10 and more small bundles which will block each other during rendering so be careful with this.
Remember that bundles are not loaded asynchronously.

We can also exclude certain scripts from bundles. They will be loaded with RequireJS when needed.
Keep in mind that Luma and Blank themes have their own exclude lists and if you are not properly fallbacking and don’t have your own exclude list, bundles will be huge since all JS modules will be bundled, regardless of whether you need them or not.

<exclude> handle takes care of files that should be excluded from bundle. As far Magento 2 is concerned – since we can’t decide on bundle content for each page, at least we can exclude assets that will not be required through the whole site, therefore bundles will consist only files that are required everywhere. As you can see in above example (Luma theme exclude list), jQuery assets are probably required everywhere so i don’t understand idea behind excluding these from bundles. Probably Magento folks wanted to leave most important stuff under RequireJS control.

Activating bundling

After we have configured our bundle size and exclude list, it is time that we turn bundling on and enjoy great performance impact.

We can turn bundling on here: Stores > configuration > advanced >developer

After bundling is ON, clear cache and switch to production mode (bundling will not work in “Developer” mode). Static files will be deployed and you will see your bundles being loaded on the Frontend.

What about performance?

We did a lot in order to reduce number of requests through the site. But, there is one small problem with performance.

This is Homepage of the Luma theme.
Testing was done on Magento 2.2.0 “dev” version, with following setting in Dev console (Chrome):

Before turning bundling ON:

  • Number of JS requests : 137
  • Size: 2.0MB
  • Loadtime: 9.46sec

With bundling turned ON:

  • Number of JS requests : 8
  • Size: 4.2MB
  • Loadtime: 20.12sec

Take a look at size and load time?
We did reduce the number of JavaScript files being loaded, but the total filesize of generated bundles is larger than the total filesize of all non-bundled JavaScript files on the frontend.
Reason? With RequireJS you load only needed JS files on a specific page. Bundling merges all JS  assets and serves them on all pages.

Conclusion

I must say I am disappointed with bundling, especially with the fact that we don’t have RequireJS optimizer by default. Without it, whole idea behind bundling is missed.

Pros:

  • Bundles multiple files into single file

Cons:

  • negates benefits of using module loader
  • filesize of bundles is larger than all non-bundled javascript files size in total (per page)
  • you can’t define bundle content precisely
  • you can’t decide in which pages which bundle will be loaded

I don’t see any reason we should use bundling right now.
Bundling will make sense in the future if Magento folks create additional functionalities for us to use.
We need configuration file where we will be able to decide on :

  • number of bundles
  • bundle size
  • bundle content
  • which pages specific bundle will be loaded

Thanks for reading and i hope we can roll up some discussion about this topic in comments 🙂

The post Javascript Bundling in Magento 2 appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/javascript-bundling-magento-2/feed/ 9
Experiences of running Magento 1 on PHP 7 http://inchoo.net/magento/experiences-running-magento-1-php-7/ http://inchoo.net/magento/experiences-running-magento-1-php-7/#comments Tue, 18 Apr 2017 11:29:50 +0000 http://inchoo.net/?p=29318 How time flies! It’s been almost a year and a half since we released Inchoo_PHP7 extension for Magento 1 (https://github.com/Inchoo/Inchoo_PHP7), and announced that in a blog post (http://inchoo.net/magento/its-alive/). We were just scratching our own itch – wanting to use all the performance benefits PHP 7 brought to run Magento faster. But, as the cover image...

The post Experiences of running Magento 1 on PHP 7 appeared first on Inchoo.

]]>
How time flies! It’s been almost a year and a half since we released Inchoo_PHP7 extension for Magento 1 (https://github.com/Inchoo/Inchoo_PHP7), and announced that in a blog post (http://inchoo.net/magento/its-alive/).

We were just scratching our own itch – wanting to use all the performance benefits PHP 7 brought to run Magento faster. But, as the cover image of that post inadvertently prophesied, we created a monster that escaped, and is continuing to terrorize the villagers to present day.

So, what happened in the meantime?

  • M1 is still going strong. M2 will take over eventually, but, in my humble personal opinion, that’s going be a very slow process.
  • On the other hand, PHP 7 is overtaking PHP 5 much quicker than previous versions used to replace their predecessors. (https://seld.be/notes/php-versions-stats-2016-2-edition). This is logical, because it really brings huge performance improvements, and it really is quite compatible, considering the major version number change.
  • Magento core became PHP 5.6 compatible in 1.9.3.x. Inchoo_PHP7 became PHP 7.1 compatible.
    • But, believe it or not, my humble personal opinion is that it’s better to run Magento on PHP 7.0 than 7.1 (https://github.com/Inchoo/Inchoo_PHP7/wiki/RunningOnPHP7.1).
      It’s really difficult to say, but I guess there are hundreds of M1 sites powered by Inchoo_PHP. Just Inchoo alone built from scratch or upgraded double figure of sites to PHP 7. Community seems to be going strong with it too, so I think I can say that it is quite tried and true by now.
  • With help from community, we found and fixed a lot of edge cases, and can say quite comfortably that we can make pretty much any M1 site work on PHP 7 without a problem.
    • And, in the last release, we even created a testing shell script, which can be very useful to find and fix potential problems.
    • Just keep in mind that this still is, and always will be, a developer-level extension. It can be “install and forget” if you are lucky, but, it would be good to have someone knowledgeable to set up, test everything and fix any issues. We had clients who came to us just for this, and we were always able to help them upgrade to PHP 7. And I don’t even want to say you need Inchoo to set it up. There are a number of developers and agencies in the Magento community that can help you out.

What’s ahead?

  • Looks like M1 core will continue it’s evolution. Going to Composer install, PHP 7, full page cache on CE, etc. – these are all things that clients are demanding and Magento experts are able to provide. Whether someone likes it or not, the whole ecosystem is large enough to be able to live and evolve on its own, directed by market pressures, and not someone’s will.
  • 3rd party extensions are the part of code we can’t fix with our extension. So, whether you are a 3rd party extension creator, a client, an integrator or anywhere else in community, please help spread the awareness that it’s good for everyone to be PHP 7 compatible.
  • PHP 7.2 will remove Mcrypt extension, and M1 core is quite dependant on it. There are a few workarounds we already tried out, but it’s not going to be pretty. For the time being, stick to PHP 7.0, and you won’t have problems.
  • Personally, I can’t wait for the moment when I’ll be able to use PHP 7 features when programming Magento, not just it’s performance. Stronger typing, for one, should bring less bugs and a more secure system. But that is still in far future from now, unfortunately.

 

TL;DR;

M1 runs (much) faster on PHP 7. Quite easy to set up due to a 3rd party module Inchoo_PHP7. MIT license. Great community support.

If you are having issues with performance of your Magento store, feel free to contact us to check technical state of your shop!

See you around!

The post Experiences of running Magento 1 on PHP 7 appeared first on Inchoo.

]]>
http://inchoo.net/magento/experiences-running-magento-1-php-7/feed/ 1
Making FedEx api show shipping estimate http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/ http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/#comments Mon, 10 Apr 2017 12:42:56 +0000 http://inchoo.net/?p=29265 There always comes the time when shopkeeper decides that he want’s to inform his customer of shipping estimate on checkout, so they could know approximately when they will get their goods. And for that, many shops today rely on API-s like ones from USPS or FedEx. Both of which are available for Magento. In this article...

The post Making FedEx api show shipping estimate appeared first on Inchoo.

]]>
There always comes the time when shopkeeper decides that he want’s to inform his customer of shipping estimate on checkout, so they could know approximately when they will get their goods. And for that, many shops today rely on API-s like ones from USPS or FedEx. Both of which are available for Magento.

In this article I will be showing you how to override FedEx carrier to return shipping estimate for given rates.

Overriding carrier

FedEx carrier works by sending request with given flags to FedEx server, who then, based on given flags, prepares response. After Magento receives response, it parses each rate as “Mage_Shipping_Model_Rate_Result” which he will later pass on to “Mage_Sales_Model_Quote_Address_Rate”. From which we will be able to access in template to show in frontend. For that, we will first override “Mage_Usa_Model_Shipping_Carrier_Fedex”.

Adding a flag for api

For FedEx API to know that it needs to return shipping estimate it needs to be given a “ReturnTransitAndCommit” flag. If we look into “Mage_Usa_Model_Shipping_Carrier_Fedex::_formRateRequest()” method, we will see that all it does is prepare flags into an array before it is sent to be parsed into request.

All we need to do here is rewrite original method and at the end of the array add our own flag.

Like in given example:

public function _formRateRequest($purpose)
{
   $ratesRequest = parent::_formRateRequest($purpose);
   $ratesRequest['ReturnTransitAndCommit'] = true ;//Here we are adding flag
   return $ratesRequest;
}

Storing the response data

After we receive data from api it will be parsed into stdClass,and in that class we are only interested in ‘RateReplyDetails’ array, which holds all our rates and their details, including their shipping estimates.

What we need to do here is pass our shipping estimate data into rate that will be passed on. For that we will be rewriting “Mage_Usa_Model_Shipping_Carrier_Fedex::_prepareRateResponse()” method.

protected function _prepareRateResponse($response)
{
   $costArr = array();
   $priceArr = array();
   // Array in which to store timestamp
   $deliveryTimeStamp = array();         //
   $errorTitle = 'Unable to retrieve tracking';
 
   if (is_object($response)) {
       if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
           if (is_array($response->Notifications)) {
               $notification = array_pop($response->Notifications);
               $errorTitle = (string)$notification->Message;
           } else {
               $errorTitle = (string)$response->Notifications->Message;
           }
       } elseif (isset($response->RateReplyDetails)) {
           $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
 
           if (is_array($response->RateReplyDetails)) {
               foreach ($response->RateReplyDetails as $rate) {
                   $serviceName = (string)$rate->ServiceType;
                   if (in_array($serviceName, $allowedMethods)) {
                       $amount = $this->_getRateAmountOriginBased($rate);
                       $costArr[$serviceName]  = $amount;
                       $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
                       //Store timestamp into prepared array
                       $deliveryTimeStamp[$serviceName] = $rate->DeliveryTimestamp; //
                   }
               }
               asort($priceArr);
           } else {
               $rate = $response->RateReplyDetails;
               $serviceName = (string)$rate->ServiceType;
               if (in_array($serviceName, $allowedMethods)) {
                   $amount = $this->_getRateAmountOriginBased($rate);
                   $costArr[$serviceName]  = $amount;
                   $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
               }
           }
       }
   }
 
   $result = Mage::getModel('shipping/rate_result');
   if (empty($priceArr)) {
       $error = Mage::getModel('shipping/rate_result_error');
       $error->setCarrier($this->_code);
       $error->setCarrierTitle($this->getConfigData('title'));
       $error->setErrorMessage($errorTitle);
       $error->setErrorMessage($this->getConfigData('specificerrmsg'));
       $result->append($error);
   } else {
       foreach ($priceArr as $method=>$price) {
           $rate = Mage::getModel('shipping/rate_result_method');
           $rate->setCarrier($this->_code);
           $rate->setCarrierTitle($this->getConfigData('title'));
           $rate->setMethod($method);
           $rate->setMethodTitle($this->getCode('method', $method));
           $rate->setCost($costArr[$method]);
           $rate->setPrice($price);
	   //Store timestamp into rate
           $rate->setDeliveryTimeStamp($deliveryTimeStamp[$method]); //
           $result->append($rate);
       }
   }
   return $result;
}

Almost done..

Before we start celebrating we will notice that we are still not getting that data in fronted. That is because we stored that data into “Mage_Shipping_Model_Rate_Result_Method” which is is not same type of rate as in template, where we have “Mage_Sales_Model_Quote_Address_Rate”. So we will additionally rewrite method “Mage_Sales_Quote_Address_Rate::importShippingRate()” and just pass our data whether its present or not.

public function importShippingRate(Mage_Shipping_Model_Rate_Result_Abstract $rate)
{
   parent::importShippingRate($rate);
   if ($rate instanceof Mage_Shipping_Model_Rate_Result_Method) {
       $this->setDeliveryTimeStamp($rate->getDeliveryTimeStamp());
   }
   return $this;
}

And we are done. Wherever you call for rates (in my case it was checkout/onepage/shipping_method/available.phtml – onepages shipping methods) fedEx api will provide shipping estimate.

Thanks for being with me and happy coding.

The post Making FedEx api show shipping estimate appeared first on Inchoo.

]]>
http://inchoo.net/magento/make-magento-fedex-api-show-shipping-estimate/feed/ 1
Wireframing a successful design for your online store http://inchoo.net/magento/design/wireframing-successful-design-online-store/ http://inchoo.net/magento/design/wireframing-successful-design-online-store/#respond Wed, 05 Apr 2017 10:33:18 +0000 http://inchoo.net/?p=29245 As designers, we’re often faced with a lot of questions about our process. We never just dive into design and bask in the glory of amazing typography and brilliant color schemes because without the phases that precede it – it just wouldn’t even begin to be possible. Beam me up, designer The worst feedback you...

The post Wireframing a successful design for your online store appeared first on Inchoo.

]]>
As designers, we’re often faced with a lot of questions about our process. We never just dive into design and bask in the glory of amazing typography and brilliant color schemes because without the phases that precede it – it just wouldn’t even begin to be possible.

Beam me up, designer

The worst feedback you can give to a designer is commenting how pretty the design is. We don’t aim for pretty, can’t learn from it and our clients aren’t always satisfied with just pretty. In the end, if our clients’ customers don’t see the use in the pretty that we’ve created – the webshop will fail at some point.

Our process includes mechanisms that minimise that risk and allow us to make informed decisions for a successful webshop. Before the actual design phase, we go through planning and wireframing.

At this point we answer some of the questions that even we can’t answer just by being designers. We have best practice knowledge and experience, but we need that custom touch. If we can take a sneak peek at your Google Analytics or Hotjar, we can answer everything from what resolutions we should be making our designs on (according to most visits on certain devices) to how descriptive we should make our labels (if the age group of your returning customers is a bit older).

Depending on if the project budget allows us, we’ll sit through Hotjar recordings to find out where the hiccups are on your current site and what user flows are actually not flowing so well. Our redesign or incremental changes need to perform better than that. The work becomes directed and concentrated like a tractor beam on a star ship. We want to pull your customers in, and do so pain free.

All work, some play

Along with all that analysing, we communicate with our clients – a lot.

We sit at our table a lot, drink coffee too much, headphones in ears – we look like we’re doing great in our own little world. The truth is there is always some communication going on. So. Much. Communication. Especially in the wireframing phase where a lot of relevant work gets done.

That’s great though – in some ways we, as designers, feel like the glue that keeps the project connected. We estimate the size of the project, provide input on what could work for the online store (through user flows and usability best practices) and we even get to draw it up. We get to experiment on features and be the ones to present it to the clients. Our wireframes, design layouts and quick prototypes are much more budget-friendly than actual implementation. All the while talking to frontend, backend, fellow design colleagues and of course – our clients.

There is a lot of dread amongst designers when it comes to client work and communication, but, we don’t think of this communication as trying to fight our client on every argument. Your client knows his business better than you, he knows the direction he wants to take it in – you need him just as much as he needs you, so, talk it out.

Say your client can’t visualise a wishlist flow or he doesn’t quite see how animating tiles on tablet will save vertical space and leave more room for products – wireframe it, prototype it, present it, resolve the issue.

                         

It’s not all black&white

This entire article went by without mentioning the latest design trends, without complaining how hard it is for us to get the exact design we have in our heads approved and how we’re missing proper tools to correspond to responsive demands.

The least of our problems is opening our tools and creating the design – it has its own challenges, but that’s on myself and my skills. It’s everything else – the planning, knowing the technology, the communication – that ends up being some of the hardest work and our best efforts in creating meaningful experiences and a successful online shop.

The post Wireframing a successful design for your online store appeared first on Inchoo.

]]>
http://inchoo.net/magento/design/wireframing-successful-design-online-store/feed/ 0
How I learned to stop worrying and love 3rd party modules for Magento http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/ http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/#comments Tue, 21 Mar 2017 11:34:50 +0000 http://inchoo.net/?p=29041 It’s just another day at the office. “Coffee tastes best on a Friday morning”, you think to yourself while reading emails from the previous day. Couple of guys are shouting in the back, and you’re shushing them angrily. With noise slowly diminishing, you find yourself checking an email with a request for custom change on...

The post How I learned to stop worrying and love 3rd party modules for Magento appeared first on Inchoo.

]]>
It’s just another day at the office. “Coffee tastes best on a Friday morning”, you think to yourself while reading emails from the previous day. Couple of guys are shouting in the back, and you’re shushing them angrily. With noise slowly diminishing, you find yourself checking an email with a request for custom change on one of your Magento stores. “Guys, we’ve got work to do. WeHeart3rdPartyModules Ltd. just requested a custom change on one of their stores. Who is willing to take on this one?”. Silence takes over the room. The only sound you hear is a fan from your colleagues PC (no need to ask why, Magento 2 is deploying static content). It seems like you’re in this alone. “Damn, I was hoping for a slow Friday”.

While PHPStorm is loading up, you’re reading the requirements:

Hi guys,

we would like to add a new column to our sales orders grid. We understand we have 20 of them already, but we really need this one – coupon description. Can you do this for us?

Thanks


“You mean rule description, not coupon, noob!”
, a smirk appears on your face. “I’ve done this a couple of times already. Also, I think we have an article written about this exact problem”.

Using your google-fu, an article pops up on the screen: How to extend Magento Order Grid? says the title. “Just what I need. This will be a piece of cake”.

IDE is finally loaded, and you’re rushing to get this done as fast as possible. “app, code, local, Inchoo” – your mind shouts – “you’ve done this a million times before” – but then you stop, and dark cloud just hangs over you. “Now I remember why there were no volunteers. This project has over a hundred 3rd party modules in local pool alone”. Not only your Friday is ruined, but the entire weekend does not look promising either.

“Well, let’s dig in, then.” Your train of though is something along the lines: “they have over 100 modules.. email mentions some earlier changes on sales order grid (having 20 columns already).. there must be a 3rd party module enabling them, or.. have we implemented this change already?”. You jump to Inchoo_WeHeart module’s config.xml and see no rewrites on 'sales_order_grid' block. It must be 3rd party then. Lovely.

Fortunately, your IDE is good in identifying class hierarchy, and in just a couple of clicks you find the class rewrite, and 3rd party module responsible (actually several of them, but two are disabled):  Amasty extended order grid. This should be fun. Module allows us to select columns to add to “Sales > Orders” grid. Adrenaline spikes up – “what if the column we need can be added via admin? That would be so great”. You check system.xml and adminhtml.xml, run to the admin and start looking for columns. “Of course it’s not possible, it was too good to be true”.

You’ve been in this situation before, and you do the usual routine: start randomly opening 3rd party module’s classes hoping to find something so obvious that would allow you to finish this as fast as possible. Of course, you fail. Looks like a more systematic approach is needed after all. You’re checking config.xml, opening up observers, helpers, and many other classes, tracing the code. Finally you find it. It’s so obvious now.

Now, off to coupon description field. You go through Magento database, and find that coupon description is located in 'salesrule_coupon' table, but there is no description. Description is in another table – 'salesrule'. Looks like we’ll need 2 SQL joins for this. “It’s just a few lines”, a voice in your head says, “no one will notice it. And it’s not a core code, right?”. After a short struggle, you resist the temptation and decide not to change the module’s code directly (sell your soul for a quick fix).

Copying the code to local pool (if the module is in community) is also an option, but we can, and should, do better than that. Is class rewrite good enough? In most situations, yes, and this is one of those situations too. You quickly whip up a rewrite: “I can do this in my sleep”, you think to yourself, but somehow manage to spend 45 minutes on getting it to work (damn XML typo).

<?php
 
class Inchoo_WeHeart_Helper_Ogrid_Columns extends Amasty_Ogrid_Helper_Columns
{
 
    function prepareOrderCollectionJoins(&$collection, $orderItemsColumns = array()){
 
        parent::prepareOrderCollectionJoins($collection, $orderItemsColumns);
 
        $collection->getSelect()->joinLeft(
            array(
                'salesrule_coupon' => $collection->getTable('salesrule/coupon')
            ),
            'order.coupon_code = salesrule_coupon.code',
            array('salesrule_coupon.rule_id as coupon_rule_id')
        )->joinLeft(
            array(
                'salesrule_rule' => $collection->getTable('salesrule/rule')
            ),
            'salesrule_rule.rule_id = salesrule_coupon.rule_id',
            array('salesrule_rule.description as coupon_description')
        );
 
    }
 
    function getDefaultFields(){
 
        if (!$this->_defaultField){
            $this->_defaultField = array_merge(parent::getDefaultFields(), array(
                'discount_description' => array(
                    'header' => Mage::helper('sales')->__('Discount Description'),
                    'index' => 'coupon_description',
                    'filter_index' => 'salesrule_rule.description'
                )
            ));
        }
 
        return $this->_defaultField;
    }
 
}

You look satisfied with the solution. Module has been changed, but in an unobtrusive way. Original logic has been kept, and new functionality added. With a few keystrokes, code is committed to a feature branch, and pushed to dev server. You test it there once more, and it looks like it’s working. Response message is sent:

“Hi WeHeart”, (no need to call them by their full name, they’re used to it), “changes have been made and pushed to dev server for you to test. Regards, Inchoo.

You close down the browser, and check the time: “Oh, looks like it’s time for my break”.

The post How I learned to stop worrying and love 3rd party modules for Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/learned-stop-worrying-love-3rd-party-modules-magento/feed/ 2
Using PostCSS with Sass http://inchoo.net/magento/magento-frontend/using-postcss-sass/ http://inchoo.net/magento/magento-frontend/using-postcss-sass/#comments Tue, 07 Mar 2017 07:37:01 +0000 http://inchoo.net/?p=28940 In this article we’ll be looking to a basic overview of PostCSS from the perspective of a developer whose current CSS development process includes use of CSS preprocessor, or in particular, Sass. If you’re a Sass user, there are a couple of approaches when starting out with PostCSS. You could make a complete switch and...

The post Using PostCSS with Sass appeared first on Inchoo.

]]>
In this article we’ll be looking to a basic overview of PostCSS from the perspective of a developer whose current CSS development process includes use of CSS preprocessor, or in particular, Sass. If you’re a Sass user, there are a couple of approaches when starting out with PostCSS. You could make a complete switch and recreate your basic preprocessing environment with PostCSS, or you could start using it as a supplement.

Many of you will say that you still only rely on your favorite preprocessor, but then, it’s possible that you’re also using Autoprefixer for vendor prefixing, and guess what? In this case, you have already included PostCSS into your workflow.

What exactly are we talking about?

PostCSS is a tool, or basically, just an API which handles its plugins written in JavaScript.

Comparing to Sass, which has a bunch of features out of the box, PostCSS comes as a blank plate, ready to be filled with the ingredients you need.

Basic Setup

Including PostCSS into your project is not a complicated process, especially if you have a basic experience of using some of the task runners, such as Gulp or Grunt.

As a simple example, let’s take a look at the following gulpfile.js.

var gulp = require('gulp'),
    postcss = require('gulp-postcss'),
    autoprefixer = require('autoprefixer');
 
gulp.task('css', function() {
  return gulp.src('src/style.css')
    .pipe(postcss(
      autoprefixer()
    ))
    .pipe(gulp.dest('dest/style.css'));
});

What we see here is a two step process:

  1. First, we include the main PostCSS module.
  2. Next, we add PostCSS plugin(s) we want to use (which in these short example is the only one – Autoprefixer).

Of course, like with any new gulp plugin which you include into your gulpfile.js, PostCSS module and any additional PostCSS plugin need to be installed first. This can be done in a terminal, with a simple command, familiar to all Gulp users:

npm install gulp-postcss autoprefixer --save-dev

Choosing plugins

So, which plugins do we need? Well, this comes to your individual choice. For an easy start or just for supplementing your preprocessing workflow with some additional power, you will certainly gain an instant benefit with these two:

  • Autoprefixer – probably the most popular PostCSS plugin, used for adding required vendor prefixes. As already mentioned at the beginning, there is high chance that you’re already using this one.
  • .box {
      display: flex;
    }
     
    // Result after processing
    .box {
      display: -webkit-box;
      display: -webkit-flex;
      display: -ms-flexbox;
      display: flex;
    }

  • Stylelint – a linting plugin useful for maintaining consistent conventions and avoiding errors in your stylesheets.

If you want to get in more deeper and recreate your basic Sass environment, most likely you’ll also need to require the following plugins:

$blue: #056ef0;
$column: 200px;
 
.menu_link {
    background: $blue;
    width: $column;
}
 
// Result after processing
.menu_link {
    background: #056ef0;
    width: 200px;
}
    • Postcss-nested – gives us a functionality of unwrapping nested rules like how Sass does it.
.phone {
    &_title {
        width: 500px;
        @media (max-width: 500px) {
            width: auto;
        }
    }
}
 
// Result after processing
.phone_title {
    width: 500px;
}
@media (max-width: 500px) {
    .phone_title {
        width: auto;
    }
}
@define-mixin icon $network, $color: blue {
    .icon.is-$(network) {
        color: $color;
        @mixin-content;
    }
    .icon.is-$(network):hover {
        color: white;
        background: $color;
    }
}
 
@mixin icon twitter {
    background: url(twt.png);
}
@mixin icon youtube, red {
    background: url(youtube.png);
}
 
// Result after processing
.icon.is-twitter {
    color: blue;
    background: url(twt.png);
}
.icon.is-twitter:hover {
    color: white;
    background: blue;
}
.icon.is-youtube {
    color: red;
    background: url(youtube.png);
}
.icon.is-youtube:hover {
    color: white;
    background: red;
}

One of the most interesting plugins that we’re mentioning last, is CSSNext. This is actually a collection of plugins that, together, give us a possibility to use the latest CSS syntax today. It transforms new CSS specs into more compatible CSS without a need to waiting for browser support. CSSNext has a lot of features and some of them are:

  • custom properties set & @apply
  • custom properties & var()
  • custom selectors
  • color() function
  • :any-link pseudo-class, etc.

In your CSS file you can do something like this:

// Example for custom properties set & @apply
:root {
  --danger-theme: {
    color: white;
    background-color: red;
  };
}
 
.danger {
  @apply --danger-theme;
}

Why should you use PostCSS?

So, if you already have an effective workflow and you’re satisfied with using your favorite preprocessor for some time now, you might be still asking yourself why do I need to learn another tool (or make the switch from Sass)? What are the benefits?

To answer these questions, let’s summarize some of the advantages:

  • Speed – even though in the meantime Sass got a significantly faster (e.g., LibSass), PostCSS is still the winner here
  • Modularity – reduces bloat; you only include the functionality that you need
  • Lightweight – with previous benefit, you get also this one
  • Immediate implementation – if you want a new functionality, you don’t have to wait for Sass to be updated; you can make it on your own

Of course, everything’s not ideal and there are also certain drawbacks:

  • Increased complexity – more planning is required (e.g., plugins must be called in a specific order)
  • A different syntax (compared to Sass)
  • PostCSS processing requires valid CSS

What’s next

It’s perfectly clear that PostCSS is all about the plugins. At the time of writing, there are more than 200 plugins available (and this number is only getting bigger). So, to go beyond the basics, you’ll need to search for other plugins that will extend this barebones setup.

Of course, if you find out that some handy functionality is missing, go ahead and solve the problem by making your own PostCSS plugin.

The post Using PostCSS with Sass appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-frontend/using-postcss-sass/feed/ 1
How to improve your Magento store performance by using Fastly http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/ http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/#comments Tue, 28 Feb 2017 12:02:45 +0000 http://inchoo.net/?p=28890 If you’re looking for a way to improve your user’s experience in terms of speed – try Fastly. What is Fastly? Fastly is a modern CDN service – it uses SSD disks in their cache servers to ensure fast content access & great cache hit ratio, it offers over 30 POP (point of presence) locations...

The post How to improve your Magento store performance by using Fastly appeared first on Inchoo.

]]>
If you’re looking for a way to improve your user’s experience in terms of speed – try Fastly.

What is Fastly?

Fastly is a modern CDN service – it uses SSD disks in their cache servers to ensure fast content access & great cache hit ratio, it offers over 30 POP (point of presence) locations placed on strategic places all over the world (Asia, Europe, North & South America, Australia, New Zeland), it uses reverse proxying which means that the content is being fetched from your origin server as it’s requested and on top of that – they have greatly supported extensions for Magento 1 & Magento 2.

Benefits

  • Faster load times for web and mobile users
  • Better site stability in the event of traffic surges
  • Easy to configure with your Magento store

Installation

In this article, I will show you how to install and configure Fastly CDN extension for Magento 2.
To install the extension, just follow instructions from their Github repository.

You may choose between three installation methods – composer installation, installation through the Magento Marketplace and manual installation by downloading the zip file.

I will use composer as installation method.

1. Open terminal \ console, go to your Magento installation directory and type these two commands in the following order:

composer config repositories.fastly-magento2 git "https://github.com/fastly/fastly-magento2.git"

Then:

composer require fastly/magento2

Once the installation is completed, enable the Fastly CDN module:

bin/magento module:enable Fastly_Cdn

Immediately after that, run the setup:upgrade command:

bin/magento setup:upgrade

And finally, clear the cache:

bin/magento cache:clean

You can read more detailed step by step instructions here.

That’s it, you have successfully installed the Fastly CDN extension. Let’s move to configuration.

Configuration

In order to use the Fastly Cdn extension, you will have to register a free Fastly account.

Once you register and verify your account, login to Fastly:

Fastly wizard

You will see a welcome wizard with two input fields which you should fill with:

  • Your website domain for Fastly to use when routing requests
  • The hostname (or IP address) and port number for your origin server

On the next screen, Fastly is offering you to enable gzip, logging and health check of your origin – you can enable this later. Click continue.

On the final screen, you will have to point your CNAME to Fastly. Doing this, you will direct traffic from the Internet through Fastly instead of immediately through your store. You can read more here on how to achieve this.


Once you’ve finished with pointing your CNAME to Fastly, let’s configure Magento.

Login to you Magento admin and go to:
Stores > Configuration > Advanced > System

Under the Full page cache tab, untick the Use system value checkbox next to the Caching Application and choose Fastly CDN.

Click on Fastly Configuration tab and enter your Fastly Service ID* and Fastly API key**.

*To find out you Service ID, login to the Fastly dashboard, locate your Service name and click on the Show Service ID link.

**To find out your API key, while in the Fastly dashboard, select Account from the user menu and scroll way down to the bottom of the page. In the Account API Key area, click the Show button.

You can press the Test credentials button just to make sure that you have entered valid credentials.

If you have received a success message, press the Save Config button and clear cache by going to System > Cache Management.

Once you have cleared the cache, go back to Stores > Configuration > Advanced > System and click on the Fastly Configuration. The final step is to upload the VCL to Fastly. You can do this by pressing the Upload VCL to Fastly button.

The modal window will pop up, make sure that the Activate VCL after upload is ticked and press the Upload button in the top right corner:

Once the upload process is done, the modal window will automatically close and the success message will show:

That’s it, you have successfully configured your Magento store with the Fastly CDN.

Advanced configuration

You can configure Advanced options by clicking the Advanced Configuration tab under the Fastly configuration. You have a detailed description under every option – read it and configure it according to your needs.

You can read more about advanced configuration here.

Purging

You can purge Fastly CDN content through the Magento admin by going to System > Cache Management. You can purge content by the following options:

  • Purge by content type
  • Purge by store
  • Purge a URL
  • Purge all

You can read more about purging here.

The post How to improve your Magento store performance by using Fastly appeared first on Inchoo.

]]>
http://inchoo.net/magento-2/how-to-improve-your-magento-store-performance-by-using-fastly/feed/ 9
Session storage and influence on performance in large PHP applications http://inchoo.net/magento/programming-magento/session-storage-php/ http://inchoo.net/magento/programming-magento/session-storage-php/#respond Tue, 07 Feb 2017 12:32:30 +0000 http://inchoo.net/?p=28686 Session is something that PHP developers use in their everyday work. But how many of you did took some time to actually consider where are they stored and how does that impact your application? Should you even care? Does number of sessions influence performance of your application? Setting up the test for file-based sessions To test this,...

The post Session storage and influence on performance in large PHP applications appeared first on Inchoo.

]]>
Session is something that PHP developers use in their everyday work. But how many of you did took some time to actually consider where are they stored and how does that impact your application? Should you even care? Does number of sessions influence performance of your application?

Setting up the test for file-based sessions

To test this, lets create some code. Keep in mind that same apply to larger applications such us Magento as well, but for the purposes of this article, we will keep it simple:

<?php
 
// Start profiling
$start = microtime(true);
 
// Set header
header('Content-type: text/plain');
 
// Adjust session config
ini_set('session.save_path', __DIR__ . '/sessions/');
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 10);
ini_set('session.gc_maxlifetime', 3000);
 
// Init session
session_start();
 
// End profiling
$duration = (microtime(true) - $start);
 
// Output the data
echo number_format($duration, 12) . PHP_EOL;

As you can see from the code above, the script doesn’t actually do anything. It allows you to set session configuration (to allow easier testing) while the only operational code here is – starting a session.

Before we go any further, let’s revise what those session configuration settings actually mean (though, you should already know this):

  • save_path – This is self-explanatory. It is used to set location of session files. In this case this is on file system, but as you will se later, this is not always the case.
  • gc_probability – Together with gc_divisor setting, this config is used to determine probability of garbage collector being executed.
  • gc_divisor – Again, used to determine probability of garbage collector being executed. This is actually calculated as gc_probability/gc_divisor.
  • gc_maxlifetime – Determines how long should session be preserved. It doesn’t mean that it will be deleted after that time, just that it will be there for at least that long. Actuall lifetime depends on when the garbage collector will be triggered.

Results of file-based session storage

To complete the test, I have executed 1000 curl requests to my server and stored the result returned by our ‘profiler’. With that data, I have created histogram of the response time:

As you can see, nothing really happens, application is working well in all cases. On the other hand, these are only 1000 sessions on extremely simple script, so let’s generate better environment. For the purpose of the test, I have used 14 days. For the number of sessions per day, I have opened random Google Analytics account of one of my Magento project, and determined that we will use 40’000 session per day. Roughly saying, we need 550’000 sessions to begin testing, which were generated by executing same amount of CURL requests to the server.

So we are ready to check what happens. I have executed another 1000 curl requests, again logging the response and generating the histogram of results:

session stored in file with GB turned on

As you can see, the results are very different. We know have two kind of results – ones that are executed properly, and ones that have severe performance penalty (700% longer execution time than usual). So what happened here? Well, simply said – garbage collector was triggered.

Explaining the results

If you recall from the beginning of the post (or take a look at the source), we defined some probability of GC being triggered. This means that given the calculate probability, some requests (to say at random) will trigger GC. This doesn’t mean that actual sessions will be deleted, but all of them will be checked, and delete if required. And that exactly is the problem.

Looking back at the script, we have defined the probability of GC being triggered as 1/10, and that is exactly what is observed on image above – 900 out of 1000 executed properly, while the rest 100 which executed GC took significantly longer to process. To confirm our findings, we will adjust gc_probability to 0 and thus disable GC. Repeating the same test, we observe following:

session stored in file with GB turned off

Now, we have only one data set, and that is the first group from the graph before. Difference in execution time is minimal, and application runs evenly across all requests. One may say that this s the solution to the problem, but keep in mind that currently nothing is deleting those session from our storage.

And last thing to note here, that in the example above with th GC turned on, due to my settings, none of the session files were actually deleted. When I did trigger deletion of the files, it took about 27 seconds to clear 90% of the files. If this is going to occur on the production server, you would have nasty problems during those 27 seconds.

Setting up the test for redis-based sessions

Next, I have tried what happens if you put those sessions into Redis server, instead of keeping them in files. For that, we need, among other things, altered version of our script:

<?php
 
// Start profiling
$start = microtime(true);
 
// Set header
header('Content-type: text/plain');
 
// Adjust session config
ini_set('session.save_handler', 'redis');
ini_set('session.save_path',    'tcp://127.0.0.1:6379');
ini_set('session.gc_probability', 0);
ini_set('session.gc_divisor', 10);
ini_set('session.gc_maxlifetime', 30000);
 
// Init session
session_start();
 
// End profiling
$duration = (microtime(true) - $start);
 
// Output the data
echo number_format($duration, 12) . PHP_EOL;

There is a very small difference in code, we only told PHP to used redis-php module to store sessions into Redis server specified by IP. Since I know nothing will happen with lower number of sessions in storage, I went and regenerated those 550’000 session before executing any tests.

Results of redis-based session storage

With everything read, we can execute the tests, the same way we previously did:

Once completed, I have adjusted gb_probabilty gain, and repeated the test:

Explaining the results

Unlike the previous test with file-based sessions, there is not really a difference in performance here. And this is basically because Redis internally can deal with session lifetime, which means that PHP does not have to. In other words, GC settings related to session no longer have influence on application performance.

Conclusion

Looking back at two examples, you can clearly see the difference between two storage types. Even though file storage is somewhat faster for majority of requests, it becomes an issue with large number of files. Even though only smaller portion of session files is actually deleted, when GC is triggered, all files will be checked. You can overcome this by disabling GC, but keep in mind that in such case, you must setup your own GC to serve the same purpose (cron process that relays on file system time-stamps). Of course, you can better time it, and it is enough to execute it once per day to lower stress, but it needs to exists.

On the other hand, you can use Redis, which is somewhat slower. How much slower, it depends on your setup. In my case, Redis was running on the same machine and we can observe performance penalty of 1ms in average. If the setup is different and Redis is running on remote server, you can expect heavy impact on the performance (for an example, in case of poor connection between servers).

My recommendation would be to use files with custom GC when application is running on single web server. If you have Multi-node setup, than Redis will be better option for you, but keep an extra eye on speed of the linke between your servers.

The post Session storage and influence on performance in large PHP applications appeared first on Inchoo.

]]>
http://inchoo.net/magento/programming-magento/session-storage-php/feed/ 0
Implementing javascript minifier http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/ http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/#comments Tue, 17 Jan 2017 13:08:47 +0000 http://inchoo.net/?p=27989 Implementing javascript minimization in Magento can help your page load time by compressing your javascript files and making them smaller for users to download. Along with CSS minimisation it can be a great asset for decreasing page loading time. In this article I will primary cover where minimization should be implemented, since writing a full code for...

The post Implementing javascript minifier appeared first on Inchoo.

]]>
Implementing javascript minimization in Magento can help your page load time by compressing your javascript files and making them smaller for users to download. Along with CSS minimisation it can be a great asset for decreasing page loading time.

In this article I will primary cover where minimization should be implemented, since writing a full code for dependable minifier is not a small task.

Although any custom php minimization code for javascript can be used, I will use slightly adjusted version of open source php minifier I tested and found really good written. Changes are done mainly to adapt it to Magento best practices, functionality remains the same. Open source PHP Javascript Minifier link.

First step in implementing this should be simple config switch in order to be able to turn it off for debugging purposes (place this is system.xml):

<?xml version="1.0"?>
<config>
    <sections>
        <dev>
            <groups>
                <js>
                    <fields>
                        <minimize_js translate="label">
                            <label>Minimize JavaScript Files</label>
                            <frontend_type>select</frontend_type>
                            <source_model>adminhtml/system_config_source_yesno</source_model>
                            <sort_order>20</sort_order>
                            <show_in_default>1</show_in_default>
                            <show_in_website>1</show_in_website>
                            <show_in_store>1</show_in_store>
                        </minimize_js>
                    </fields>
                </js>
            </groups>
        </dev>
    </sections>
</config>

Set the default value in config.xml by adding:

<default>
        <dev>
            <js>
                <minimize_js>0</minimize_js>
            </js>
        </dev>
</default>

Add declaration of core data helper rewrite also in your config.xml :

<helpers>
        <core>
             <rewrite>
                     <data>Inchoo_Minifier_Helper_Core_Data</data>
             </rewrite>
        </core>
</helpers>

Adjust the rewritten core helper code where Magento merges javascript, so we also minify it after merging (changes are marked between “INCHOO EDIT” in comments, and function minimizeJs is added, which serves as main handler of minifier code we’re about to add):

class Inchoo_Minifier_Helper_Core_Data extends Mage_Core_Helper_Data
{
    /**
     * INCHOO EDIT - added call for JS minimizing
     * @param array $srcFiles
     * @param bool $targetFile
     * @param bool $mustMerge
     * @param null $beforeMergeCallback
     * @param array $extensionsFilter
     * @return bool|string
     */
    public function mergeFiles(array $srcFiles, $targetFile = false, $mustMerge = false,
                               $beforeMergeCallback = null, $extensionsFilter = array())
    {
        try {
            // check whether merger is required
            $shouldMerge = $mustMerge || !$targetFile;
            if (!$shouldMerge) {
                if (!file_exists($targetFile)) {
                    $shouldMerge = true;
                } else {
                    $targetMtime = filemtime($targetFile);
                    foreach ($srcFiles as $file) {
                        if (!file_exists($file) || @filemtime($file) > $targetMtime) {
                            $shouldMerge = true;
                            break;
                        }
                    }
                }
            }
 
            // merge contents into the file
            if ($shouldMerge) {
                if ($targetFile && !is_writeable(dirname($targetFile))) {
                    // no translation intentionally
                    throw new Exception(sprintf('Path %s is not writeable.', dirname($targetFile)));
                }
 
                // filter by extensions
                if ($extensionsFilter) {
                    if (!is_array($extensionsFilter)) {
                        $extensionsFilter = array($extensionsFilter);
                    }
                    if (!empty($srcFiles)){
                        foreach ($srcFiles as $key => $file) {
                            $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                            if (!in_array($fileExt, $extensionsFilter)) {
                                unset($srcFiles[$key]);
                            }
                        }
                    }
                }
                if (empty($srcFiles)) {
                    // no translation intentionally
                    throw new Exception('No files to compile.');
                }
 
                $data = '';
                foreach ($srcFiles as $file) {
                    if (!file_exists($file)) {
                        continue;
                    }
                    $contents = file_get_contents($file) . "\n";
                    if ($beforeMergeCallback && is_callable($beforeMergeCallback)) {
                        $contents = call_user_func($beforeMergeCallback, $file, $contents);
                    }
                    $data .= $contents;
                }
                if (!$data) {
                    // no translation intentionally
                    throw new Exception(sprintf("No content found in files:\n%s", implode("\n", $srcFiles)));
                }
                if ($targetFile) {
                    /** INCHOO EDIT START **/
                    if(isset($file)){
                        // Minimize only .js files
                        $fileExt = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                        if(Mage::getStoreConfigFlag('dev/js/minimize_js') && $fileExt === 'js'){
                            $data = $this->minimizeJs($data);
                        }
                    }
                    /** INCHOO EDIT END **/
                    file_put_contents($targetFile, $data, LOCK_EX);
                } else {
                    return $data; // no need to write to file, just return data
                }
            }
 
            return true; // no need in merger or merged into file successfully
        } catch (Exception $e) {
            Mage::logException($e);
        }
        return false;
    }
 
    /**
     * INCHOO - main JS minimizer function
     * @param $data
     * @return bool|string
     */
    public function minimizeJs($data)
    {
        $minifer = Mage::helper('inchoo_minifier/minifier');
        $result = $minifer->minify($data);
 
        if($result !== false) {
            return $result;
        }
        return $data;
    }
}

Lastly, minifier code we will use to compress our merged javascript files:

<?php
/**
 * Copyright (c) 2009, Robert Hafner
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Stash Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Robert Hafner BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Magento port adjustment by Tomislav Nikčevski, 2016
 * JS Minifier
 * Usage - $this->minify($js);
 */
 
class Inchoo_Minifier_Helper_Minifier
{
    /**
     * The input javascript to be minified.
     *
     * @var string
     */
    protected $_input;
 
    /**
     * Output javascript buffer - this will be returned
     * @var
     */
    protected $_text;
 
    /**
     * The location of the character (in the input string) that is next to be
     * processed.
     *
     * @var int
     */
    protected $_currentPosition = 0;
 
    /**
     * The first of the characters currently being looked at.
     *
     * @var string
     */
    protected $_firstChar = '';
 
    /**
     * The next character being looked at (after firstChar);
     *
     * @var string
     */
    protected $_secondChar = '';
 
    /**
     * This character is only active when certain look ahead actions take place.
     *
     *  @var string
     */
    protected $_endChar;
 
    /**
     * Contains lock ids which are used to replace certain code patterns and
     * prevent them from being minified
     *
     * @var array
     */
    protected $locks = array();
 
    /**
     * Takes a string containing javascript and removes unneeded characters in
     * order to shrink the code without altering it's functionality.
     *
     * @param  string      $js      The raw javascript to be minified
     * @return bool|string
     */
    public function minify($js)
    {
        try {
            $js = $this->lock($js);
 
            $this->initialize($js);
            $this->loop();
            $js = $this->unlock(ltrim($this->_text));
            $this->clean();
 
            return $js;
 
        } catch (Exception $e) {
            $this->clean();
            Mage::log('Minifier failed. Error: ' . $e->getMessage());
            return false;
        }
    }
 
    /**
     *  Initializes internal variables, normalizes new lines,
     *
     * @param string $js      The raw javascript to be minified
     */
    protected function initialize($js)
    {
        $js = str_replace("\r\n", "\n", $js);
        $js = str_replace('/**/', '', $js);
        $this->_input = str_replace("\r", "\n", $js);
 
        // We add a newline to the end of the script to make it easier to deal
        // with comments at the bottom of the script- this prevents the unclosed
        // comment error that can otherwise occur.
        $this->_input .= PHP_EOL;
 
        // Populate "firstChar" with a new line, "secondChar" with the first character, before
        // entering the loop
        $this->_firstChar = "\n";
        $this->_secondChar = $this->getReal();
    }
 
    /**
     * The primary action occurs here. This function loops through the input string,
     * outputting anything that's relevant and discarding anything that is not.
     */
    protected function loop()
    {
        while ($this->_firstChar !== false && !is_null($this->_firstChar) && $this->_firstChar !== '') {
 
            switch ($this->_firstChar) {
                // new lines
                case "\n":
                    // if the next line is something that can't stand alone preserve the newline
                    if (strpos('(-+{[@', $this->_secondChar) !== false) {
                        $this->_text .= $this->_firstChar;
                        $this->saveString();
                        break;
                    }
 
                    // if secondChar is a space we skip the rest of the switch block and go down to the
                    // string/regex check below, resetting secondChar with getReal
                    if($this->_secondChar === ' ')
                        break;
 
                // otherwise we treat the newline like a space
 
                case ' ':
                    if(static::isAlphaNumeric($this->_secondChar))
                        $this->_text .= $this->_firstChar;
 
                    $this->saveString();
                    break;
 
                default:
                    switch ($this->_secondChar) {
                        case "\n":
                            if (strpos('}])+-"\'', $this->_firstChar) !== false) {
                                $this->_text .= $this->_firstChar;
                                $this->saveString();
                                break;
                            } else {
                                if (static::isAlphaNumeric($this->_firstChar)) {
                                    $this->_text .= $this->_firstChar;
                                    $this->saveString();
                                }
                            }
                            break;
 
                        case ' ':
                            if(!static::isAlphaNumeric($this->_firstChar))
                                break;
 
                        default:
                            // check for some regex that breaks stuff
                            if ($this->_firstChar === '/' && ($this->_secondChar === '\'' || $this->_secondChar === '"')) {
                                $this->saveRegex();
                                continue;
                            }
 
                            $this->_text .= $this->_firstChar;
                            $this->saveString();
                            break;
                    }
            }
 
            // do reg check of doom
            $this->_secondChar = $this->getReal();
 
            if(($this->_secondChar == '/' && strpos('(,=:[!&|?', $this->_firstChar) !== false))
                $this->saveRegex();
        }
    }
 
    /**
     * Resets attributes that do not need to be stored between requests so that
     * the next request is ready to go. Another reason for this is to make sure
     * the variables are cleared and are not taking up memory.
     */
    protected function clean()
    {
        unset($this->_input);
        $this->_currentPosition = 0;
        $this->_firstChar = $this->_secondChar = $this->_text = '';
        unset($this->_endChar);
    }
 
    /**
     * Returns the next string for processing based off of the current index.
     *
     * @return string
     */
    protected function getChar()
    {
        // Check to see if we had anything in the look ahead buffer and use that.
        if (isset($this->_endChar)) {
            $char = $this->_endChar;
            unset($this->_endChar);
 
            // Otherwise we start pulling from the input.
        } else {
            $char = substr($this->_input, $this->_currentPosition, 1);
 
            // If the next character doesn't exist return false.
            if (isset($char) && $char === false) {
                return false;
            }
 
            // Otherwise increment the pointer and use this char.
            $this->_currentPosition++;
        }
 
        // Normalize all whitespace except for the newline character into a
        // standard space.
        if($char !== "\n" && ord($char) < 32)
 
            return ' ';
 
        return $char;
    }
 
    /**
     * This function gets the next "real" character. It is essentially a wrapper
     * around the getChar function that skips comments. This has significant
     * performance benefits as the skipping is done using native functions (ie,
     * c code) rather than in script php.
     *
     *
     * @return string            Next 'real' character to be processed.
     */
    protected function getReal()
    {
        $startIndex = $this->_currentPosition;
        $char = $this->getChar();
 
        // Check to see if we're potentially in a comment
        if ($char !== '/') {
            return $char;
        }
 
        $this->_endChar = $this->getChar();
 
        if ($this->_endChar === '/') {
            return $this->processOneLineComments($startIndex);
 
        } elseif ($this->_endChar === '*') {
            return $this->processMultiLineComments($startIndex);
        }
 
        return $char;
    }
 
    /**
     * Removed one line comments, with the exception of some very specific types of
     * conditional comments.
     *
     * @param  int    $startIndex The index point where "getReal" function started
     * @return string
     */
    protected function processOneLineComments($startIndex)
    {
        $thirdCommentString = substr($this->_input, $this->_currentPosition, 1);
 
        // kill rest of line
        $this->getNext("\n");
 
        if ($thirdCommentString == '@') {
            $endPoint = $this->_currentPosition - $startIndex;
            unset($this->_endChar);
            $char = "\n" . substr($this->_input, $startIndex, $endPoint);
        } else {
            // first one is contents of $this->_endChar
            $this->getChar();
            $char = $this->getChar();
        }
 
        return $char;
    }
 
    /**
     * Skips multiline comments where appropriate, and includes them where needed.
     * Conditional comments and "license" style blocks are preserved.
     *
     * @param  int               $startIndex The index point where "getReal" function started
     * @return bool|string       False if there's no character
     * @throws \RuntimeException Unclosed comments will throw an error
     */
    protected function processMultiLineComments($startIndex)
    {
        $this->getChar(); // current endChar
        $thirdCommentString = $this->getChar();
 
        // kill everything up to the next */ if it's there
        if ($this->getNext('*/')) {
 
            $this->getChar(); // get *
            $this->getChar(); // get /
            $char = $this->getChar(); // get next real character
 
            // Now we reinsert conditional comments and YUI-style licensing comments
            if ($thirdCommentString === '!' || $thirdCommentString === '@') {
 
                // If conditional comments or flagged comments are not the first thing in the script
                // we need to append firstChar to text and fill it with a space before moving on.
                if ($startIndex > 0) {
                    $this->_text .= $this->_firstChar;
                    $this->_firstChar = " ";
 
                    // If the comment started on a new line we let it stay on the new line
                    if ($this->_input[($startIndex - 1)] === "\n") {
                        $this->_text .= "\n";
                    }
                }
 
                $endPoint = ($this->_currentPosition - 1) - $startIndex;
                $this->_text .= substr($this->_input, $startIndex, $endPoint);
 
                return $char;
            }
 
        } else {
            $char = false;
        }
 
        if($char === false)
            throw new \RuntimeException('Unclosed multiline comment at position: ' . ($this->_currentPosition - 2));
 
        // if we're here endChar is part of the comment and therefore tossed
        if(isset($this->_endChar))
            unset($this->_endChar);
 
        return $char;
    }
 
    /**
     * Pushes the index ahead to the next instance of the supplied string. If it
     * is found the first character of the string is returned and the index is set
     * to it's position.
     *
     * @param  string       $string
     * @return string|false Returns the first character of the string or false.
     */
    protected function getNext($string)
    {
        // Find the next occurrence of "string" after the current position.
        $pos = strpos($this->_input, $string, $this->_currentPosition);
 
        // If it's not there return false.
        if($pos === false)
 
            return false;
 
        // Adjust position of index to jump ahead to the asked for string
        $this->_currentPosition = $pos;
 
        // Return the first character of that string.
        return substr($this->_input, $this->_currentPosition, 1);
    }
 
    /**
     * When a javascript string is detected this function crawls for the end of
     * it and saves the whole string.
     *
     * @throws \RuntimeException Unclosed strings will throw an error
     */
    protected function saveString()
    {
        $startpos = $this->_currentPosition;
 
        // saveString is always called after a gets cleared, so we push secondChar into
        // that spot.
        $this->_firstChar = $this->_secondChar;
 
        // If this isn't a string we don't need to do anything.
        if ($this->_firstChar !== "'" && $this->_firstChar !== '"') {
            return;
        }
 
        // String type is the quote used, " or '
        $stringType = $this->_firstChar;
 
        // append out that starting quote
        $this->_text .= $this->_firstChar;
 
        // Loop until the string is done
        while (true) {
 
            // Grab the very next character and load it into firstChar
            $this->_firstChar = $this->getChar();
 
            switch ($this->_firstChar) {
 
                // If the string opener (single or double quote) is used
                // output it and break out of the while loop-
                // The string is finished!
                case $stringType:
                    break 2;
 
                // New lines in strings without line delimiters are bad- actual
                // new lines will be represented by the string \n and not the actual
                // character, so those will be treated just fine using the switch
                // block below.
                case "\n":
                    throw new \RuntimeException('Unclosed string at position: ' . $startpos );
                    break;
 
                // Escaped characters get picked up here. If it's an escaped new line it's not really needed
                case '\\':
 
                    // firstChar is a slash. We want to keep it, and the next character,
                    // unless it's a new line. New lines as actual strings will be
                    // preserved, but escaped new lines should be reduced.
                    $this->_secondChar = $this->getChar();
 
                    // If secondChar is a new line we discard firstChar and secondChar and restart the loop.
                    if ($this->_secondChar === "\n") {
                        break;
                    }
 
                    // append the escaped character and restart the loop.
                    $this->_text .= $this->_firstChar . $this->_secondChar;
                    break;
 
                // Since we're not dealing with any special cases we simply
                // output the character and continue our loop.
                default:
                    $this->_text .= $this->_firstChar;
            }
        }
    }
 
    /**
     * When a regular expression is detected this function crawls for the end of
     * it and saves the whole regex.
     *
     * @throws \RuntimeException Unclosed regex will throw an error
     */
    protected function saveRegex()
    {
        $this->_text .= $this->_firstChar . $this->_secondChar;
 
        while (($this->_firstChar = $this->getChar()) !== false) {
            if($this->_firstChar === '/')
                break;
 
            if ($this->_firstChar === '\\') {
                $this->_text .= $this->_firstChar;
                $this->_firstChar = $this->getChar();
            }
 
            if($this->_firstChar === "\n")
                throw new \RuntimeException('Unclosed regex pattern at position: ' . $this->_currentPosition);
 
            $this->_text .= $this->_firstChar;
        }
        $this->_secondChar = $this->getReal();
    }
 
    /**
     * Checks to see if a character is alphanumeric.
     *
     * @param  string $char Just one character
     * @return bool
     */
    protected static function isAlphaNumeric($char)
    {
        return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
    }
 
    /**
     * Replace patterns in the given string and store the replacement
     *
     * @param  string $js The string to lock
     * @return bool
     */
    protected function lock($js)
    {
        /* lock things like <code>"asd" + ++x;</code> */
        $lock = '"LOCK---' . crc32(time()) . '"';
 
        $matches = array();
        preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
        if (empty($matches)) {
            return $js;
        }
 
        $this->locks[$lock] = $matches[2];
 
        $js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
        /* -- */
 
        return $js;
    }
 
    /**
     * Replace "locks" with the original characters
     *
     * @param  string $js The string to unlock
     * @return bool
     */
    protected function unlock($js)
    {
        if (empty($this->locks)) {
            return $js;
        }
 
        foreach ($this->locks as $lock => $replacement) {
            $js = str_replace($lock, $replacement, $js);
        }
 
        return $js;
    }
 
}

That’s it, enable your minifier in admin under System->Configuration->Developer->JavaScript Settings->Minimize Javascript Files -> Yes.

Good luck, and don’t forget to clear cache.

In case you feel you need some extra help, we can offer you a detailed custom report based on our technical audit – feel free to get in touch and see what we can do for you!

The post Implementing javascript minifier appeared first on Inchoo.

]]>
http://inchoo.net/magento/programming-magento/implementing-javascript-minifier/feed/ 5
Custom data components in Pimcore http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/ http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/#comments Tue, 10 Jan 2017 12:00:55 +0000 http://inchoo.net/?p=28447 Today I will show you how to create custom data component in Pimcore 4.4.. If you are using Pimcore, sooner or later you will end up with requirement for new form element (complex type). Data components in Pimcore are object types used for complex data modeling on object. Data component consists from data, tag Extjs...

The post Custom data components in Pimcore appeared first on Inchoo.

]]>
Today I will show you how to create custom data component in Pimcore 4.4.. If you are using Pimcore, sooner or later you will end up with requirement for new form element (complex type). Data components in Pimcore are object types used for complex data modeling on object. Data component consists from data, tag Extjs scripts and one Model which is responsible for saving/loading configuration. Currently Pimcore has 19 complex data components (you can read more on https://www.pimcore.org/docs/latest/Objects/Object_Classes/Data_Types/index.html), and they will cover most requirements you will ever need. However, if (when) you’ll need a custom data component, here’s how you can create it.

Custom component

For demonstration we’ll create custom data component which will use source from custom controller and show data as select field (dropdown). Also we’ll cover how to add custom options in data script and use it on tag script. First, we’ll create plugin skeleton with basic plugin data in plugins folder and our folder will be called “Customdatacomponent”. If you want to know how to create plugin please read article “Extending Pimcore with plugins” on our blog. After we have created plugin skeleton it’s time to work on the component configuration script.

Data script

Data script is required for component configuration, and without this script our custom component will not be displayed in Pimcore. Let’s create script on location [Pimcore root]/plugins/Customdatacomponent/static/js/data-components/data/customcomponent.js, and include script in plugin configuration (plugin.xml):

<pluginJsPaths>
	<path>/plugins/Customdatacomponent/static/js/data-components/data/customcomponent.js</path>
</pluginJsPaths>

and we can write some code after that. First, we need to register pimcore.object.classes.data.customdatacomponent namespace and add new component class in pimcore.object.classes.data which will create class from pimcore.object.classes.data.data. To make it all work we also need to set type, allowed in, initialize logic, type name, icon class and group. We also need to work on getLayout method where we’ll add one custom option. But first, let’s write complete data script:

/**
 * Custom data component - configuration
 */
 
pimcore.registerNS("pimcore.object.classes.data.customdatacomponent");
pimcore.object.classes.data.customdatacomponent = Class.create(pimcore.object.classes.data.data, {
 
    type: "customdatacomponent",
 
    /**
     * define where this datatype is allowed
     */
    allowIn: {
        object: true,
        objectbrick: false,
        fieldcollection: false,
        localizedfield: false,
        classificationstore : false,
        block: true
    },
 
    initialize: function (treeNode, initData) {
        this.type = "customdatacomponent";
 
        this.initData(initData);
 
        this.treeNode = treeNode;
    },
 
    getTypeName: function () {
        return 'Custom data component';
    },
 
    getIconClass: function () {
        return "pimcore_icon_select";
    },
 
    getGroup: function () {
        return "select";
    },
 
    getLayout: function ($super) {
        $super();
 
        this.specificPanel.removeAll();
        this.specificPanel.add([
            {
                xtype: "textfield",
                fieldLabel: 'Custom option',
                name: "customoption",
                value: this.datax.customoption
            }
        ]);
 
        return this.layout;
    },
 
    applyData: function ($super) {
        $super();
        delete this.datax.options;
    },
 
    applySpecialData: function(source) {
        if (source.datax) {
            if (!this.datax) {
                this.datax =  {};
            }
        }
    }
 
});

You can see we have added Custom option (text field) in get layout method. We’ll call that value in tag script for label value. For demonstration we have set data component to be available only on objects (allowIn array). This is how it should look on data components menu:

customdatacomponent-1

and this is how it should look on configuration form:

customdatacomponent-2

Model

To save and load data into component configuration we’ll need to create corresponding model. When you click on save button in class form, our custom data component Pimcore will lookup for model in Pimcore\Model\Object\ClassDefinition\Data namespace, file with Customdatacomponent name and class Customdatacomponent which extends Model\Object\ClassDefinition\Data\Select. Now we need to create model on path [Pimcore root]/plugins/Customdatacomponent/lib/Pimcore/Model/Object/ClassDefinition/Data/Customdatacomponent.php. As you can see, starting point for autoloader in our plugin is in lib folder. Here is the model logic:

<?php
namespace Pimcore\Model\Object\ClassDefinition\Data;
 
use Pimcore\Model;
 
class Customdatacomponent extends Model\Object\ClassDefinition\Data\Select
{
 
    /**
     * Static type of this element
     *
     * @var string
     */
    public $fieldtype = "customdatacomponent";
 
    public $customoption;
 
    /** Restrict selection to comma-separated list of countries.
     * @var null
     */
    public $restrictTo = null;
 
    public function __construct()
    {
    }
 
    public function setcustomoption($customoption)
    {
        $this->customoption = $customoption;
    }
 
    public function getcustomoption()
    {
        return $this->customoption;
    }
}

We have extended Model\Object\ClassDefinition\Data\Select class because our custom data component is also select type and we already have all the logic. For custom option we’ll create class variable “customoption”, and the name matches as option added in data component. We also need setter and getter methods (setcustomoption and getcustomoption). Now we can test save and load configuration for our component on class form.

Tag script

We’ll create tag script which will be used to render data component. First, we’ll create the whole script logic and then I will explain every part. For this example we’ll cover getLayoutEdit method which is used by edit form. Create data script on location [Pimcore]/plugins/Customdatacomponent/static/js/data-components/tags/customcomponent.js

/**
 *
 */
pimcore.registerNS("pimcore.object.tags.customdatacomponent");
pimcore.object.tags.customdatacomponent = Class.create(pimcore.object.tags.select, {
 
    type: "customdatacomponent",
 
    initialize: function (data, fieldConfig) {
        this.data = data;
        this.fieldConfig = fieldConfig;
    },
 
    getLayoutEdit: function ($super) {
        $super();
 
        this.storeoptions = {
            autoDestroy: true,
            proxy: {
                type: 'ajax',
                url: "/plugin/Customdatacomponent/index/values",
                reader: {
                    type: 'json',
                    rootProperty: 'id'
                }
            },
            fields: ["id", "value", "text"],
            listeners: {
                load: function() {
                }.bind(this)
            }
        };
 
        this.store = new Ext.data.Store(this.storeoptions);
 
        var options = {
            name: this.fieldConfig.title,
            triggerAction: "all",
            editable: true,
            typeAhead: true,
            forceSelection: true,
            selectOnFocus: true,
            fieldLabel: this.fieldConfig.customoption,
            store: this.store,
            componentCls: "object_field",
            width: 250,
            style: "margin: 10px",
            labelWidth: 100,
            valueField: "value"
        };
 
        if (this.fieldConfig.width) {
            options.width = this.fieldConfig.width;
        } else {
            options.width = 300;
        }
 
        options.width += options.labelWidth;
 
        if (this.fieldConfig.height) {
            options.height = this.fieldConfig.height;
        }
 
        if (typeof this.data == "string" || typeof this.data == "number") {
            options.value = this.data;
        }
 
        this.component = new Ext.form.ComboBox(options);
 
        this.store.load();
 
        return this.component;
    }
});

After we set type and initialize class, we have created getLayoutEdit logic. First, we need to create storeoptions which are used to create store that will store pulled data from the custom controller. Data will be formatted to populate options for dropdown. For options we need to set proxy to point on url “/plugin/Customdatacomponent/index/values” and enable auto destroy and fields which will be returned by controller per item. After we create store options we’ll instantiate Ext.data.Store class. Next thing is to create options for component. Most of them are generic, but it’s important to set name (from configuration object), field label and store (instantiated store). Notice that we have set fieldLabel to value of our customoption from configuration to demonstrate how to use custom option field from data script on tag script. After options we need to create Ext.form.ComboBox, load store and return instantiated component. This is how our component looks like in object form:

customdatacomponent-3

Controller

Only thing we are missing to have data for tag script is custom controller. We’ll create custom controller on location [Pimcore root]/plugins/Customdatacomponent/controllers/IndexController.php

<?php
 
class Customdatacomponent_IndexController extends \Pimcore\Controller\Action\Admin
{
    public function indexAction()
    {
 
        // reachable via http://your.domain/plugin/Customdatacomponent/index/index
    }
 
    public function valuesAction()
    {
 
        $response = array(
            array(
                'id' => 0,
                'value' => 0,
                'text' => 'Value 0'
            ),
            array(
                'id' => 1,
                'value' => 1,
                'text' => 'Value 1'
            ),
            array(
                'id' => 2,
                'value' => 2,
                'text' => 'Value 2'
            )
        );
 
        return $this->_helper->json($response);
    }
}

Our logic is in valuesAction method. As you can see, we have prepared sample array with fields defined in store configuration in tag script. Important thing is to return array as json, for that we’ll use json method from our helper. Note that we are extending \Pimcore\Controller\Action\Admin class because it may contain sensitive data, so only admin should be able to access it.

Conclusion and Github

Now you have a basic knowledge on how to extend data components in Pimcore and you can additionaly improve our plugin to cover, for example, grid logic for select field. Hope this will help you to understand data components and Pimcore itself. Thanks for reading, soon we’ll cover more topics for extending Pimcore.

You can find our example plugin on https://github.com/zoransalamun/pimcore-custom-data-component

However, if you’re having trouble with Magento or Pimcore or need our technical assistance, feel free to drop us a note – we’ll be glad to check out your code and help you with development or review your site and help you get rid of any bugs there.

The post Custom data components in Pimcore appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-integration/custom-data-components-pimcore/feed/ 2
How many Magento certified people do you know? http://inchoo.net/magento/magento-certifications/ http://inchoo.net/magento/magento-certifications/#comments Mon, 02 Jan 2017 11:29:40 +0000 http://inchoo.net/?p=28388 How many Magento certified people are out there? 5,288 to date, according to the official data at everyone’s disposal. What about their distribution – where do they come from, what countries have the most certified individuals? In this post you’ll find an overview of one interesting metric – Magento certified individuals per capita. Who tops...

The post How many Magento certified people do you know? appeared first on Inchoo.

]]>
How many Magento certified people are out there? 5,288 to date, according to the official data at everyone’s disposal. What about their distribution – where do they come from, what countries have the most certified individuals?

In this post you’ll find an overview of one interesting metric – Magento certified individuals per capita. Who tops the charts and where does your country stand? Read on to find out.

We’ve seen many different rankings in the Magento world, and thanks to Ben Marks and one of his recent tweets, I took it upon myself to dig just a bit deeper around the number of Magento certified people (totals include developers and solution specialists) per capita.

The initial conversation was about whether Netherlands or Croatia are leading these rankings globally, but as it turns out after I took a closer look at the official numbers, there are actually countries doing way better than us.

Why per capita?

Ok, but why use this metric in the first place? Well, it’s rather difficult to compare countries using absolute numbers and if we combine absolutes with something like this (number of Magento certified people per 1M of general population in each country) and then analyze the data, we can see some interesting trends and put things in a different perspective.

For example, we can see how in some countries the efforts of several individuals and companies can really make a difference and impact their local communities – and this is exactly what the metrics in this case are showing or hinting at.

The methodology behind this one was rather simple and straightforward:

    1. Go to official Magento certification directory
    2. Select country after country and simply collect these figures
    3. Then use some type of comparable statistics on population per country (rather difficult to find as each country has their own methods but I chose this one – the data set used was of December 29th, 2016)

Limitations of this method

It’s impossible to see people who have opted not to be visible in the directory, so the numbers are not 100% representative of the real situation with certifications.

Another one is – there are people (app. 140 out of 5288 listed) who don’t have any country associated with them, so these have not made the numbers.

And finally – smaller countries can improve their position more easily. But then again, there are only two countries with less than 1M people in Top 50 so that’s not that big of an issue.

You can check out some of the hard data below, but for the sake of this post, I’ll focus more of the analysis on the thing I found most interesting – finding out just how many certified Magento people per 1M of the total population in each of the countries globally.

The “hard data”

  • there are 5288 certified individuals in Magento directory
  • they come from 73 countries
  • 7 countries have only one certified individual
  • 11 countries have more than 100 certified people
  • USA has the highest number of certified individuals – 849

Baltic countries top the charts

magento-certified-per-1m

While it may come as a surprise that Latvia is the one leading the pack, Scandiweb team have been doing an amazing job over there in the last years, getting a lot of their employees certified in the process, and building on the community in this Baltic country. We’ve also gotten quite fond of them as they sent the largest delegation to Developers Paradise 2016 in Opatija so we’re happy to see our Latvian friends in the significant lead with 41,4 Magento certified individuals per one million people.

In not that close second, we’ll find Malta. Now, this one is the only really debatable country to make it into Top 5 as it doesn’t make sense to say they have 30,3 people per 1M if they have the total of 13 people certified (and the total population of less than 500k). They are also the only country besides Montenegro to have at least one person certified and the population of less than 1M, so they do deserve to get some credit in any case.

We can either take them out of the chart or change the metric to certified per 100k people (which we can do, or not), but after them there’s another Baltic country, a Latvian neighbour – Estonia – with 20,5 Magento certified people per 1M.

Yes, it helps that these three countries topping the charts have 1-2 million people (or much less in case of Malta), but still – the smaller the country, the smaller the pool of people you can find those willing to dedicate their everyday work to a specific eCommerce software, so these numbers do hold value.

The Netherlands is hot and getting hotter

And then we have Netherlands – and hats off to them and everyone around an amazing community they’ve created as they are only one of two countries with >10M people who made it into Top 10. Can you guess the second one? Well, it wasn’t that difficult to spot Ukraine, right?

If you add to that one of the latest similar investigation by Willem de Groot (who many of you will know as the man behind MageReport) which shows that the Dutch are way ahead of everyone in number of Magento stores per 1M, it clearly demonstrates the strength and impact Magento and its community has had on the businesses in this country, and vice versa.

Not sure whether Magento’s official orange color had something to do with the ease of its penetration to this market, but let’s leave that aside for someone else to analyze 🙂

What about Croatia?

The reason why I started this was, of course, a chance to do some humblebragging as well, since we invested a lot of efforts of our own into Magento and local community of developers, and Croatia is in fact right there, in Top 5, with just over 12 Magento certified people per 1M, chasing after our friends from Netherlands – let’s see how the tables will turn in 2017. It would be really nice to see how our city of Osijek would rank on a similar chart if we compared cities globally, but that one is much more difficult to get right (proper data, cities vs metropolitan areas etc.).

More charts

For you who like to play around with numbers, here are some more charts to check out. Click on the thumbnails for full images.

A) USA (849) holds a slim lead over India (804) in the total number of certified individuals, and these two together take over 30% share of the global total of all Magento certified people.
magento-certified-per-country

B) European countries hold almost half of all certificates globally, while having just 10% of the global population.

C) Magento certified per 1M – a list of Top 25 countries
magento-certified-per-1m-top25

So, what now?

If you’re Magento certified yourself, jump on to the directory to check if you have a city/country listed next to your name to see if you made these numbers and helped your country out 🙂 If there’s something missing, there’s always the FAQ to help you sort it out.

And let’s all keep an eye out on these numbers and use them to further improve on the co-opetition within Magento ecosystem.

With these metrics, chances are we won’t have that much influence on the total population numbers, but we can all pull our weight when it comes to the certifications – we’re already preparing some of the Inchooers for new exams to get closer to and, hopefully, surpass our Dutch friends soon 🙂

Keep them coming!

The post How many Magento certified people do you know? appeared first on Inchoo.

]]>
http://inchoo.net/magento/magento-certifications/feed/ 6
How to set up a CDN (Amazon CloudFront) in Magento http://inchoo.net/magento/set-up-cdn-in-magento/ http://inchoo.net/magento/set-up-cdn-in-magento/#comments Wed, 14 Dec 2016 12:32:58 +0000 http://inchoo.net/?p=28088 If you are using Amazon AWS CloudFront and you want to set up CDN in your Magento, this tutorial is going to take you step by step on how to set up your CloudFront and Magento to get it both working. STEP 1 – DISTRIBUTIONS The first thing you have to do is to set...

The post How to set up a CDN (Amazon CloudFront) in Magento appeared first on Inchoo.

]]>
If you are using Amazon AWS CloudFront and you want to set up CDN in your Magento, this tutorial is going to take you step by step on how to set up your CloudFront and Magento to get it both working.

STEP 1 – DISTRIBUTIONS

The first thing you have to do is to set up Distribution on CloudFront. Below you can see an example of my custom Distribution.

CloudFront Distributions

This is how general options should look like, of course you can edit them to suit your needs.

CloudFront general tab

As you may see here:CloudFront certificate

Amazon’s CloudFront uses its own certificate so if your site uses SSL you can just use CloudFronts’s domain name such as “https://xyz13zxy.cloudfront.net”. You can also use custom CDN url with SSL such as “https://cdn.yoursite.com” but then you’ll have to import your own certificate.

Two very important options you need to be careful about are:

Alternate Domain Names (CNAMEs): this is used if you want nicer URLs for your content. So for example, if your CNAME is “cdn.yoursite.com” you will have to do some configuration in your cPanel of your own server to make it work and instead of, for example “http://www.yoursite.com/images/image.jpg” it’s going to be “http://cdn.yoursite.com/images/image.jpg”

Domain Name: This is a domain name Amazon generates for you, for example: “xyz13zxy.cloudfront.net”. If you don’t care about URL for your CDN this is faster and easier way to set up CDN. So instead of, for example “http://www.yoursite.com/images/image.jpg” it’s going to be “http://xyz13zxy.cloudfront.net/images/image.jpg”.

So how Amazon knows which images to use if you are trying to access it via “xyz13zxy.cloudfront.net”. This is where the Origins come in.

STEP 2 – ORIGINS

Now you have to set Origins. Amazon’s official documentation says: “When you create or update a distribution, you provide information about one or more locations—known as origins—where you store the original versions of your web content. CloudFront gets your web content from your origins and serves it to viewers. Each origin is either an Amazon S3 bucket or an HTTP server, for example, a web server”.

Origin Domain Name: this is your source domain name, for example “www.yoursite.com

Origin ID: this is some ID you specify by yourself just to easily identify this Origin. It can be something similar to your CDN domain name.

Origin Path: it’s empty. Amazon’s official documentation says: “Optional. If you want CloudFront to request your content from a directory in your Amazon S3 bucket or your custom origin, enter the directory name here, beginning with a /. CloudFront appends the directory name to the value of Origin Domain Name when forwarding the request to your origin, for example, myawsbucket/production.”

CloudFront Origins

CloudFront Origins

STEP 3 – BEHAVIOURS

The next thing you have to do is to set Behaviours. Amazon’s official documentation says: “A cache behaviour lets you configure a variety of CloudFront functionality for a given URL path pattern for files on your website. For example, one cache behaviour might apply to all .jpg files in the images directory on a web server that you’re using as an origin server for CloudFront.

When using CDN sometimes you might get an error on your website similar to this one: “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://xyz123zxy.cloudfront.net/images/images.jpg This can be fixed by moving the resource to the same domain or enabling CORS.”

To fix this problem you have to set Behaviours.

Origin: It’s very important to choose the right Origin from the dropdown box. This is your Origin you set up in the previous step.

Whitelist Headers: choose “Origin” from the left box and click on Add >>. When you choose your other options click on Create/Edit.

Path Pattern: This is path to a file you want to allow CORS for. I’m using “*” so it matches all files and allows Cross-origin resource sharing.

CloudFront Behavior

STEP 4 – INVALIDATIONS

You might be wondering how and when the cache is going to be invalidated or cached again. By default, each object automatically expires after 24 hours.

From Amazon’s official documentation: To change the cache duration for all objects that match the same path pattern, you can change the CloudFront settings for Minimum TTL, Maximum TTL, and Default TTL for a cache behaviour. For information about the individual settings, see Minimum TTL, Maximum TTL, and Default TTL. To use these settings, you must choose the Customize option for the Object Caching setting.

If you want to invalidate cache for all files (which is not recommended) or just for one specific file, you have to set “Invalidations”.

Enter path to your own files and click on Invalidate.

CloudFront Cache Invalidation

STEP 5 – MAGENTO SETTINGS

This was CloudFront’s side, now it’s time to configure Magento’s settings. It’s really easy to configure Magento. The only thing you have to do is to configure Magento’s URLs. Screenshots below are using SSL (HTTPS) so I used “https://…” everywhere. If you don’t use SSL, but regular HTTP, then you should use ”http://…”.

As you may see, nothing has changed in Default settings.

Magento Website Settings

Choose your Website from the dropdown and change only fields where you want to set CDN URL. I chose to set CDN only for images and CSS files.

Magento Website Settings

Save your settings, clear your cache and test your website. How do you know if your site uses CDN now? Open your website (if you are using Chrome) and press CTRL and U on your keyboard to open the source code. If you can find something like “.cloudfront.net” or “cdn.yoursite.com” and all your content is loading properly that means CDN is set up properly.

Magento CDN code

The post How to set up a CDN (Amazon CloudFront) in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/set-up-cdn-in-magento/feed/ 14
We want you on stage at Meet Magento Croatia! http://inchoo.net/magento/meet-magento-croatia-speakers/ http://inchoo.net/magento/meet-magento-croatia-speakers/#comments Fri, 09 Dec 2016 13:32:26 +0000 http://inchoo.net/?p=28329 Thinking about whether you should join us for Meet Magento Croatia? If you haven’t thought about it by now, you should definitely start! Here’s why… Meet Magento Croatia is part of the global family of Meet Magento events which gathers eCommerce experts from all over the world. One, two or three-day events serve as the...

The post We want you on stage at Meet Magento Croatia! appeared first on Inchoo.

]]>
Thinking about whether you should join us for Meet Magento Croatia? If you haven’t thought about it by now, you should definitely start! Here’s why…

Meet Magento Croatia is part of the global family of Meet Magento events which gathers eCommerce experts from all over the world. One, two or three-day events serve as the best possible connection platforms for getting all the latest info from the Magento world.

And not only that – they are the perfect place where eCommerce trends are discussed and hot Magento topics tackled. Developers Paradise we organized in April proved, once again, that events channel the power of community. In fact, that’s what makes them special!

How can you join?

By signing up, of course. We’d like to see you as a speaker or, alternatively, as a delegate. We know many of you have a lot of miles in your Magento shoes and we’d like to hear all about the roads you’ve been on. It’s no secret you’ve breathed life into numerous web shops – now it’s the time to shine and share with community how you did it.

mm17hr-tekst

We are interested in hearing your story (inside out)!

Along the way, you overcame different obstacles and bridged various challenges – by sharing your story, you are empowering the community to grow. We all want to improve, we would highly appreciate you telling us how exactly you are doing it – day in, day out.

The only thing is…

There’s not much time left to apply so you better hurry!

Final deadline for getting your applications in is December 31st. We’ll make sure to review all of your applications and get back to you as soon as possible. At this point, we can promise you, if you make it to the #MM17HR stage, you’ll be around some pretty amazing Magento and eCommerce experts. 😉 We’ll be dropping names soon, so make sure to stay tuned to see who we’ll host.

So, come on – tell us what you did, and more importantly, how did you do it and we’ll get you on the MM17HR stage. 

See you in Osijek, Croatia, home of Inchooers!

The post We want you on stage at Meet Magento Croatia! appeared first on Inchoo.

]]>
http://inchoo.net/magento/meet-magento-croatia-speakers/feed/ 2
Add Pagination to Custom Collection in Magento http://inchoo.net/magento/pagination-custom-collection/ http://inchoo.net/magento/pagination-custom-collection/#comments Thu, 08 Dec 2016 12:22:14 +0000 http://inchoo.net/?p=28165 If you are starting with Magento, just like I am, you might run into the issue of adding pagination to your table, list of products or anything else you want to list on frontend. Magento allows you to create your own pagination with adjustable built-in options. This tutorial will show you how to create pagination for a custom collection...

The post Add Pagination to Custom Collection in Magento appeared first on Inchoo.

]]>
If you are starting with Magento, just like I am, you might run into the issue of adding pagination to your table, list of products or anything else you want to list on frontend. Magento allows you to create your own pagination with adjustable built-in options. This tutorial will show you how to create pagination for a custom collection in Magento.

This is how final custom pagination looks like:
magento pagination

I created Education module inside Inchoo package with Ticket model.

magento pagination

STEP 1

First of all create custom action to access your content. Add a custom action inside your controller:

public function paginationAction()
{
    $this->loadLayout();
    $this->renderLayout();
}

STEP 2

In your xml (app > design > frontend > rwd > default > layout > your_layout.xml) inside “layout” node add the node referencing your custom action. If you are using different or custom theme your path might be different than “rwd > default”.

“education/pagination” – where “education” is the name of your module, “pagination” is name of your block inside pp/code/local/Inchoo/Education/Block folder, my block is named Pagination.php.

<education_index_pagination>
  <reference name="content">
    <block name="collection_ticket" type="education/pagination" template="education/pagination.phtml"/>
  </reference>
</education_index_pagination>

Magento pagination

STEP 3

Create class that will extend Magento’s default product list toolbar so you can alter default behavior. Inside app/code/local/Inchoo/Education/Block create Toolbar.php

class Inchoo_Education_Block_Toolbar extends Mage_Catalog_Block_Product_List_Toolbar
{
    public function getPagerHtml()
    {
        $pagerBlock = $this->getLayout()->createBlock('page/html_pager');
        if ($pagerBlock instanceof Varien_Object)
        {
            /* here you can customize your toolbar like h*/
            $pagerBlock->setAvailableLimit($this->getAvailableLimit());
            $pagerBlock->setUseContainer(false)
                ->setShowPerPage(false)
                ->setShowAmounts(false)
                ->setLimitVarName($this->getLimitVarName())
                ->setPageVarName($this->getPageVarName())
                ->setLimit($this->getLimit())
                ->setCollection($this->getCollection());
            return $pagerBlock->toHtml();
        }
        return '';
    }
}

STEP 4

Create your block inside your module’s block, in this case app/code/local/Inchoo/Education/Block. I named this block Pagination, you can call it however you want to, but then you have to be careful about calling it in your xml.

class Inchoo_Education_Block_Pagination extends Mage_Core_Block_Template
{
    public function __construct()
    {
        parent::__construct();
        //set your own collection (get data from database so you can list it)
        $collection = Mage::getModel('education/ticket')->getCollection();
        $this->setCollection($collection);
    }
    protected function _prepareLayout()
    {
        parent::_prepareLayout();
        //this is toolbar we created in the previous step
        $toolbar = $this->getLayout()->createBlock('education/toolbar');
        //get collection of your model
        $collection = Mage::getModel('education/ticket')->getCollection();
        //this is where you set what options would you like to  have for sorting your grid. Key is your column in your database, value is just value that will be shown in template
        $toolbar->setAvailableOrders(array('created_at'=> 'Created Time','id'=>'ID'));
        $toolbar->setDefaultOrder('id');
        $toolbar->setDefaultDirection("asc");
        $toolbar->setCollection($collection);
        $this->setChild('toolbar', $toolbar);
        $this->getCollection()->load();
        return $this;
    }
 
    //this is what you call in your .phtml file to render toolbar
    public function getToolbarHtml()
    {
        return $this->getChildHtml('toolbar');
    }
}

STEP 5

Inside app > design > frontend > rwd > default > template (or change rwd > default to your theme path) create pagination.phtml (as you stated in step 2):

<?php $collection = $this->getCollection();  ?>
    <div class="page-title">
        <h1><?php echo $this->__('My Collection') ?></h1>
    </div>
<?php echo $this->getToolbarHtml(); ?>
    <table class="data-table" id="my-custom-table">
        <thead>
        <tr>
            <th><?php echo $this->__('Title') ?></th>
            <th><span class="nobr"><?php echo $this->__('Created at') ?></span></th>
        </tr>
        </thead>
        <tbody>
        <?php $_odd = ''; ?>
        <?php foreach ($collection as $item): ?>
            <tr>
                <td><span class="nobr"><?php echo $item->getSubject(); ?></span></td>
                <td><?php echo $this->formatDate($item->getCreatedAt()) ?></td>
            </tr>
        <?php endforeach; ?>
        </tbody>
    </table>
<?php echo $this->getToolbarHtml(); ?>

*Custom pagination

If you want to create your own custom pagination without using Magento’s Toolbar, after you finished step 1 and step 2 inside app/code/local/Inchoo/Education/Block create Pagination.php.

class Inchoo_Education_Block_Pagination extends Mage_Core_Block_Template
{
    public function __construct()
    {
        parent::__construct();
        $collection = Mage::getModel('education/ticket')->getCollection();
        $this->setCollection($collection);
    }
    protected function _prepareLayout()
    {
        parent::_prepareLayout();
        $pager = $this->getLayout()->createBlock('page/html_pager', 'custom.pager');
        $pager->setAvailableLimit(array(5=>5,10=>10,20=>20,'all'=>'all'));
        $pager->setCollection($this->getCollection());
        $this->setChild('pager', $pager);
        $this->getCollection()->load();
        return $this;
    }
    public function getToolbarHtml()
    {
        return $this->getChildHtml('pager');
    }
}

After you did this, skip step 3 and 4 and go straight to step 5 to create .phtml. When you’re done with that you’ll get something like this:

magento pagination

The post Add Pagination to Custom Collection in Magento appeared first on Inchoo.

]]>
http://inchoo.net/magento/pagination-custom-collection/feed/ 4
Road to the Code with Less Bugs? http://inchoo.net/magento/programming-magento/less-bugs/ http://inchoo.net/magento/programming-magento/less-bugs/#respond Tue, 06 Dec 2016 12:31:09 +0000 http://inchoo.net/?p=28220 I think that writing any non-trivial software without introducing bugs is close to impossible. World is complex, data used in programs is complex, handling all possible interactions of our code with said data is hard. There are probably bugs even in the short script that is accompanying this article. Sounds dark? Maybe it shouldn’t. Everybody...

The post Road to the Code with Less Bugs? appeared first on Inchoo.

]]>
I think that writing any non-trivial software without introducing bugs is close to impossible. World is complex, data used in programs is complex, handling all possible interactions of our code with said data is hard. There are probably bugs even in the short script that is accompanying this article. Sounds dark? Maybe it shouldn’t.

Everybody who writes software is creating bugs, it is inevitable.

There is huge amount of research about what causes bugs and there are various results and conclusions.

My interpretation of the surface reading is that number of “bugs per line” is scaling in some way with total number of lines in program. For example, program with 1000 lines will have one bug, program with 2000 lines will have 3 bugs, and program with 50 000 lines will have 100 bugs. Personally, I feel like that is true as it kind of matches my experience. Programmers mostly agree about this.

Another finding is that programs with more restriction about who (who = which code) and when (under which conditions) can modify some state also have less bugs. (Scientists say!) I agree, because it also matches my experience. In long functions with many variables or classes with lots of properties which are used later for logic there are more bugs. Especially if we are writing code under assumption that objects will always be in some finite amount of states that we accounted for or that variables will be configured in a certain way. (error calling Method() on null, index not found, Varien_Object returning null on getPropertty() instead of giving error)

There is also something called cyclomatic complexity (amount of logical branches code can take).
I hope there are least some of us who did not experience that feeling of dread upon reading logs and seeing an exception occurred in the middle of 500+ line method of class on a line which starts somewhere after character number 52…

So, if we could somehow:

  • Make objects/variables which are always in valid state. Maybe by using static declarations, there will be another article about how PHP7 is improvement over PHP5 here.
  • Make sections of code smaller and easier to understand. Perhaps by reducing number of branches and loops.
  • Reduce overall amount of code. Perhaps by using higher level constructs, so we write less code.

Honestly, I am not really sure if such ideas will really work. So far, my feeling is that it helps a lot. Maybe it will help more when I get more experience, maybe it is actually worse and I lack knowledge/skill to see it.

But I can see there is lot of space for improvement. And I am very confident that we should at least examine some of the options and see how they work in practice.

So, in this teaser post, let’s do something everybody enjoys!

Parsing CSV files, and doing some analysis on that data!!!

Input: CSV with UN employment data. Rows with years, work sectors and number of people working in each sector per given year (in 2002, there were 100 000 people working for the Government).

Output: Statistic with percentages that show how many people worked in some sector for given year, and how those percentages changed over years.

For the first post, we are going simple, only showing data for one country and for both genders.
That gives us excuse to introduce generators. 🙂 Our goal is to show the table in HTML (ugly looking table but we won’t be concerned with styling, only with filling table with proper data).
Performance is secondary goal, and it will not be tackled until we are satisfied with the code quality. Corectness and crash-resistance are primary goals!

I will quickly show uses of some of the concepts and we will try to improve code as series go on.
Not sure where we will end and if functional style is good fit for the problem overall but let’s see where it will take us. Along the way, we’ll also see if functional in PHP will be easier to understand than the same thing implemented in OOP way or old-school imperative approach.

Code is on github

https://github.com/grizwako/broadening_horizons

We will start with closures.

Closures are good for encapsulating data, without going full on object-oriented.
http://php.net/manual/en/functions.anonymous.php
http://culttt.com/2013/03/25/what-are-php-lambdas-and-closures/
http://fabien.potencier.org/on-php-5-3-lambda-functions-and-closures.html

Goes like this – you create some function which will have some data saved, and you can later call that function (with new parameters too).

Example function is accessing values of CSV data rows by column names defined in CSV header line.

function mappedRow(data-row, column-map)
 
$entry = mappedRow(
    [4, 'bla@blo.com'],
    ['id' => 0, 'email' => '1]
);
 
echo $entry('email'); //bla@blo.com

Implementation:

/**
 * Creates dictionary-like closure which accesses array values by string keys.
 * So instead of $row[$map['key'] you can use $row('key')
 * @param $row
 * @param $columnMap
 * @return Closure
 */
function mappedRow($row, $columnMap)
{
    if(empty($columnMap)) {
        throw new LogicException('Column map must not be empty! Hint: reverse header row to use as column map');
    }
 
    //returns function which accepts one parameter and has saved values for $row and $columnMap
    return function($key = null) use ($row, $columnMap) {
        return $row[$columnMap[$key]];
    };
}

 

Second useful thing: generators!

http://php.net/manual/en/language.generators.overview.php
Generators are like iterators/array, but differ in the fact that they will give you data only when they are told to do so. We can use generator to read huge CSV file line by line, and we only take lines that we need.

For now, you can think of keyword yield as “feed (return) value into caller, but save state of generator function for when it is called next”.

Generator is somewhat like closure, it can also have some local variables saved for later usages.

/**
 * Lazily read CSV file
 *
 * @param $filename
 * @param string $split
 * @param int $maxLineLength
 * @param array $columnMap
 * @return Generator
 * @throws Exception
 */
function mappedCsvGenerator($filename, $split = ',', $maxLineLength = 0, $columnMap = [])
{
    $fileHandle = fopen($filename,'r');
    if(FALSE === $fileHandle) {
        throw new Exception('Could not open file: '.$filename);
    }
    while (($data = fgetcsv($fileHandle, $maxLineLength, $split)) !== FALSE) {
        if(empty($columnMap)) {
            //Moves array pointer to next row
            $columnMap = array_flip($data);
            continue;
        }
 
        yield mappedRow($data, $columnMap);
    }
}

Let’s create instance of a generator; we are still not asking it to read data from CSV.

$rows = mappedCsvGenerator('un_employment_1980-2008.csv');

Next, we will not need all rows from CSV, only a few. Let’s pretend that we do not have the database system in which we can import data. Filter rows as we are only interested in data for one country and for both genders.

It would be wonderful if we could somehow delay reading of the CSV rows to the point when we actually need them.

Luckily, Nikita Popov created a really nice library that helps with using lazy collections. (Also, he is one behind a lot of cool stuff that PHP got recently. Generators included.)
About Nikita: https://nikic.github.io/aboutMe.html
Lazy collections: https://github.com/nikic/iter

$stats = iter\filter(
    function($row) {
        return $row('Country or Area') === 'Croatia'
		&& $row('Sex') === 'Total men and women';
    },
    $rows
);

CSV file is still unread!! We did not actually use the values, we only specified that the values we will use will have to satisfy some condition. Instead of creating an array and then inserting rows from some foreach, we only said that $stats will give us rows that satisfy some condition when we ask for them.

Grouping values with common higher order functions

Next step, I think that we will need to group values by work sector and year if we want to display them in a table.

Higher order just means function that accepts other function in a parameter, nothing actually fancy.
Word group sounds like it should be a function, but there is not one such in Nikic’s library.
Some searching will bring us to: https://github.com/lstrojny/functional-php

It has group, it seems it can only do one level of grouping with one call. There is some advanced stuff in the library Lars made so maybe we can do some mumbo-jumbo when we revisit the code and refactor it to compose function that will group by two levels.

First we group by work sector (subclassification). And then we do something that is actually elegant, but looks weird if you are not used to it. Map is kind of like foreach, but it is not supposed to modify collection it is iterating over. Think of it like: map = foreach item: call function “a” and return items function “a” returned.

Function “a” will accept one item from the collection, do something and return one item. Map will return array of all items function “a” returned.

$statsBySubclassification = group($stats, function($row){
    return $row('Subclassification');
});
 
$statsBySubclassificationAndYear = map($statsBySubclassification, function($subclass) {
    $indexed = reindex($subclass, function($row) {
        return (int)$row('Year');
    });
    return map($indexed, function($row) {
        return (float)$row('Value');
    });
});

So, with our grouped entries we will call function (which accepts all entries that fall under one Subclassifiction). That function will first create a new array which will have “Year” of entry as a key and after that go over all elements and get ‘Value’ only for each row.

Something like:

$statsBySubclassificationAndYear = [];
foreach($statsBySubclassification as $subclassifiction => $items) {
    foreach($items as $item) {
    	//PHP has autovivification, so we dont have to worry about creating proper array keys
        $statsBySubclassificationAndYear[$subclassifiction][(int)$item('Year')] = (float)$item('Value');
    }
}

Honestly, in PHP foreach seems easier at first, even to me.
In some made up functional language, we would have this sort of thing:

statsBySubclassificationAndYear = map(statsBySubclassification, (subclass) =>
    indexed = reindex(subclass, (row) => (int)row('Year'))
    map(indexed, (row) => (float)row('Value'))
)

 

Displaying grouped data as a table:

<?php
$totalByYear = $statsBySubclassificationAndYear['Total.'];
$years = array_keys($totalByYear);
sort($years); // Why is this bad style?
 
//now, lets build table that will show us suclassifications percentages over years
// I feel that foreach is cleaner here, because we are mutating some object
$table = new SimpleXMLElement('<table/>');
$head = $table->addChild('tr');
$head->addChild('th', 'Subclassification');
foreach($years as $year) {
    $head->addChild('th', $year);
}
 
foreach($statsBySubclassificationAndYear as $subclass => $byYear) {
    $tableRow = $table->addChild('tr');
    $tableRow->addChild('td', $subclass);
    $percentage = 0;
    //we are relying on $years that was defined too far from where we are using it
    foreach($years as $year) {
        if(array_key_exists($year, $byYear)) {
            // can this part of code be improved by using functional style?
            // we are relying on $percentage from last iteration, stuff like that causes bugs
            $tempPerc = 100 *($byYear[$year] / $totalByYear[$year]);
            $delta = $tempPerc - $percentage;
            $percentage = $tempPerc;
            $procFormat = number_format($percentage, 2);
            $deltaFormat = number_format($delta, 2);
            $tableRow->addChild('td', $procFormat)->addChild('p', $deltaFormat);
        } else {
            $tableRow->addChild('td', ' - ');
        }
    }
}
 
?>
 
<style>
    table, th, td {
        border: 1px solid black;
    }
    p {
        color: blueviolet;
    }
</style>
 
Percentages by sector, blue is delta from previous year
<?php echo $table->asXML(); ?>

Conclusion of the first part:

Article turned somewhat different than I expected. Having nicer lambda syntax would improve readability so much. Closures are great in limiting local scope, generators are really nice to use, in some other languages they require more syntax. Nikic does wonders for PHP.

We have nicely formatted data, look at this design masterpiece!
data screenshot

 Credits:

UN data

http://data.un.org/
Total employment, by economic activity (Thousands)  [1980-2008]
http://data.un.org/Data.aspx?q=employment&d=LABORSTA&f=tableCode%3a2B

https://github.com/nikic/iter
https://github.com/lstrojny/functional-php

The post Road to the Code with Less Bugs? appeared first on Inchoo.

]]>
http://inchoo.net/magento/programming-magento/less-bugs/feed/ 0
Minify your CSS and JavaScript code! http://inchoo.net/magento/minify-your-css-and-javascript-code/ http://inchoo.net/magento/minify-your-css-and-javascript-code/#comments Tue, 29 Nov 2016 14:37:07 +0000 http://inchoo.net/?p=28074 If you are reading this article, you are probably somehow involved with web development. It is also most likely that you already know how search engines use page speed as one of the parameters for evaluating your site. We have couple of options on our disposal when it comes to increasing site speed. Today we will...

The post Minify your CSS and JavaScript code! appeared first on Inchoo.

]]>
If you are reading this article, you are probably somehow involved with web development. It is also most likely that you already know how search engines use page speed as one of the parameters for evaluating your site. We have couple of options on our disposal when it comes to increasing site speed. Today we will cover one of them.

Probably the simplest method of increasing site speed is minifying CSS and JavaScript. Even though this is pretty simple and straight forward method, for some reason, a lot of Magento stores and generally websites, don’t use this benefit. We have seen different cases where developers completely ignored code minification. Also, we have seen partially minified code. Partially means that, for example, theme code is minified but there is a lot of inline code which isn’t and it is possible to relocate and minify it. We’ve also seen cases where main theme code is minified but code from some other source (extension etc.) isn’t.

Before we go any further let us just ensure that we are on the same page about what “minified code” means. So, minified code is code without:

  • Whitespace characters
  • New line characters
  • Comments
  • Block delimiters.

Let us to suggest few possible options how to minify your code for production.

Minifying CSS and JavaScript code

In order to minify code we first need to decide are we going to do it manually or automatically. We’ll suggest to do automatically in order to speed up the development process, but if for some reason you want to do it manually, we don’t mind.

Do It Manually

CSS Minifying

If you are a developer who doesn’t use any kind of frontend “wizard” on your project (SASS, LESS, Gulp, Grunt etc.), the best option for you will be to use some kind of online or offline minifying tool. For example, one of the online minifying tools you can use is cssminifier.
If you are working on a complex project, you’re probably using some kind of IDE software. In that case you can speed things up by using some kind of IDE plugin that you can install or enable. That will minify the code every time when you save the working stylesheet file.

JavaScript Minifying

Very similar to minifying CSS code. There is bunch of online tools and different kind of IDE extensions for minifying JavaScript code. For example, good online tools are jscompress or javascript-minifier.

Do it automatically

This approach highly depends on the project and what we can or can’t change on it. Sometimes, it’s not possible to change the whole development environment just like that, especially if we’re collaborating with other developers on the same project. It’s not easy to suggest the best possible solution for everyone, but we’ll share what are we using on our Magento projects. We will assume that our audience is familiar with SASS, Compass, Gulp etc. If you need more information, please check out this article from my colleague. You can learn a lot of new things there.

SASS

If you already using SASS on your project, this will be easy for you. Just add additional parameter on sass –watch command:

sass --watch file.scss:file.css --style compressed

But, if you are not familiar with SASS then keep reading, maybe you will find even something better for you.

Compass

If project has Compass included, we already have Compass config file in the project. Just search for config.rb file, open the file and you should see something that looks something like the following:

# Require any additional compass plugins here.
 
# Set this to the root of your project when deployed:
http_path = "/"
css_dir = "stylesheets"
sass_dir = "sass"
images_dir = "images"
javascripts_dir = "javascripts"
 
# You can select your preferred output style here (can be overridden via the command line):
output_style = :compressed # :expanded or :nested or :compact or :compressed
 
# To enable relative paths to assets via compass helper functions. Uncomment:
# relative_assets = true
 
# To disable debugging comments that display the original location of your selectors. Uncomment:
line_comments = false

The config.rb file, Line 11 will likely be commented with the # sign. Uncomment the output_style (remove #). Then restart Compass to start watching for changes in SCSS files. To trigger it enter the command:

$ compass watch

Gulp

Gulp is a truly useful tool for better and faster frontend development. Here on Inchoo blog we have already mentioned few Gulp benefits. We often suggest using Gulp as one of the best tools for complex projects such as Magento frontend development itself.
With Gulp we have wide range of tools at our disposal which can be used to solve different kinds of development problems. But, currently we are only interested in code magnification tasks.

There is couple of ways to handle code magnification in Gulp, but we prefer these two Gulp tasks for that:

Minify CSS and JS code in Magento 1.x

If we take Magento 1.x for example, we need to handle with a lot of inline CSS and Javascript code inside template files. And in order to achieve the best possible results with code magnification, we need to take a look at your Magento template files and find all CSS and JavaScript code that we can transfer to our theme files. Keep in mind that in some cases we will not be in position to transfer all of them to appropriate theme files. Mostly in cases where JavaScript is depending on some php input/output.

Also, we suggest that while we’re transferring JavaScript code to theme file, we double check what code we need on which page. It is possible that we don’t need everything loaded on all Magneto pages. For example, some chunk of code is required just on the product page or just on the home page. That gives us possibility of splitting code in two different files which we can call on different occasions by using Magneto XML engine.

Let us create new gulp tasks which we will using for fast minifying code:

var gulp = require('gulp');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');
 
// Css paths
var cssInput = '../sass/**/*.scss';
var cssOutput = '../css';
 
// script paths
var jsSkinInput = '../js/**/*.js';
var jsSkinOutput = '../js/';
 
// Js min task
gulp.task('skin-scripts', function() {
    return gulp.src(jsSkinInput)
        .pipe(uglify())
        .pipe(rename({
            suffix: '.min'
        }))
        .pipe(gulp.dest(jsSkinOutput));
});
 
// Sass task
gulp.task('sass', function () {
    return gulp.src(cssInput)
        .pipe(sass({
            errLogToConsole: true,
            outputStyle: 'compressed'
        }))
        .pipe(gulp.dest(cssOutput))
});
 
// Sass file watcher
gulp.task('sass-watch', function() {
    return gulp
        .watch([cssInput, cssOutput], ['sass','browser-reload'])
        .on('change', function(event) {
            console.log('Css File ' + event.path + ' was ' + event.type + ', running tasks...');
        })
});
 
// Js file watcher
gulp.task('skin-scripts-watch', function() {
    return gulp
        .watch([jsSkinInput,jsSkinOutput], ['skin-scripts','browser-reload'])
        .on('change', function(event) {
            console.log('Js File ' + event.path + ' was ' + event.type + ', running tasks...');
        });
});
 
gulp.task('default', ['skin-scripts', 'skin-scripts-watch', 'sass', 'sass-watch']);

As we can see, we are watching for changes on all JavaScript and SASS files in our theme structure. Gulp recognized it and after few seconds Gulp generated minified code at the end. The first two task (skin-scripts and sass) are responsible for CSS and JavaScript minifying, and they work very similar, only difference being that JS files are renamed after minifying process by adding suffix .min at the end of file name. We wish to keep original file for further work and adjustments.

When you are ready, enter this command in your terminal in order to start Gulp tasks:

gulp default

Sounds simple right. But with Magento there’s always something additional 🙂

The thing is that Magento uses JavaScript not just in themes scope, but on the whole system. Major part of JavaScript which is responsible for keeping the system running is located in Magento root (js folder).

As we mentioned before, our goal is to have full minified JavaScript code. And we wish to do things properly. So we have two options:

First one is to adjust our Gulp task so that our task minifies root JavaScript code also, but that isn’t the best possible option. Why? Because with that action we will additionally complicate further maintenance and Magento upgrades. So, in order to avoid that, we want to minify root JavaScript code through Magento backend adjustments. Just before Magento JavaScript code merge.

The second option have been presented by my colleague Tomislav, check the article here.

Keep reading our blog and let us know what you think in the comments below 🙂

The post Minify your CSS and JavaScript code! appeared first on Inchoo.

]]>
http://inchoo.net/magento/minify-your-css-and-javascript-code/feed/ 3
Meet Magento Croatia 2017 – save the dates! http://inchoo.net/magento/meet-magento-croatia-2017/ http://inchoo.net/magento/meet-magento-croatia-2017/#comments Thu, 17 Nov 2016 13:11:03 +0000 http://inchoo.net/?p=28028 Ready for the newest addition to the global family of Meet Magento events? We sure are! Save the dates, 17 – 18 March 2017, ’cause that’s when we’ll be seeing you in Osijek for the first ever Meet Magento Croatia. 2016 was a huge year for Inchooers. After checking off two major things from our...

The post Meet Magento Croatia 2017 – save the dates! appeared first on Inchoo.

]]>
Ready for the newest addition to the global family of Meet Magento events? We sure are! Save the dates, 17 – 18 March 2017, ’cause that’s when we’ll be seeing you in Osijek for the first ever Meet Magento Croatia.

2016 was a huge year for Inchooers.

After checking off two major things from our to do list (putting together Developers Paradise and moving to a new office), we decided 2017 will be no different. To set the tone right, we already started planning some rather exciting new things.

Meet Magento Croatia 2017 is one of those things and we can’t wait to see you there!

Yes, it’s true that Meet Magento events have a tendency to be more oriented towards the local community. We admit that’s the case in our scenario too. But, when local forces are joined by the strong international community, that’s when the magic happens.

So, we are excited to welcome you – members of the extraordinary Magento community to our hometown!

For those of you who’ve been to Developers Paradise and miss it, and for those of you who heard rumours and want to be part of the next conference organized by Inchoo – we suggest you to get your tickets now. 

Number of tickets is limited and we wouldn’t want you to miss the event ’cause you waited too long.

lobby-bar-broj-1

Why join?

Simply put – we’ll do everything that’s in our power to make it amazing. Last time it certainly paid off, we hope it will pay off this time too.

We will have separate Biz and Dev tracks with Dev track entirely in English so if you hope to tackle Magento 2 right on the spot or to share war stories with your old and some new friends – this is the place to be.

Care to share?

Interested in sharing the story of how you tackled a certain Magento 2 challenge with everyone? You can (and should!) apply as a speaker – check out the form here and let us know what you would like to speak about.

All other info (such as the location and Early Bird ticket price) can be found on this link. If there are some uncertainties left, we even got you covered with a nice FAQ.

Hopefully, this sparked your interest and we’ll see you in Osijek soon.

pogled-drava-s-hotela

Interested? Great!

The next thing you’d like to do is to book your ticket. Based on our DevParadise experience, Early Bird has a tendency of flying off really fast once it catches the worm. Now, you’d like to get your ticket before that happens, right? 😉

The post Meet Magento Croatia 2017 – save the dates! appeared first on Inchoo.

]]>
http://inchoo.net/magento/meet-magento-croatia-2017/feed/ 1
Meet Magento World brings Magento experts to your home and office http://inchoo.net/magento/meet-magento-world-2016-online-conference/ http://inchoo.net/magento/meet-magento-world-2016-online-conference/#comments Mon, 07 Nov 2016 11:26:49 +0000 http://inchoo.net/?p=28000 Meet Magento World is a new addition to Magento ecosystem – for the first time ever you’ll be able to attend a Magento conference without leaving your home or your office. On July 11th – 13th 2017 you can take part in this global online event and get direct access to all the top speakers and hottest Magento...

The post Meet Magento World brings Magento experts to your home and office appeared first on Inchoo.

]]>
Meet Magento World is a new addition to Magento ecosystem – for the first time ever you’ll be able to attend a Magento conference without leaving your home or your office.

On July 11th – 13th 2017 you can take part in this global online event and get direct access to all the top speakers and hottest Magento and eCommerce topics at once.

Read on to learn more about this event and hear from one of the top speakers – our own Ivona Namjesnik – what you can expect.

When you’re a part of Magento ecosystem, looking back at a year behind you measure it, among other things, by Meet Magento events you’ve attended – as a delegate, sponsor or a speaker. Perhaps even by the ones you organized.

2016 was packed with these, and it’s only November. So, now is as good time as any to start preparing for something completely different. For the first time ever, in July 2017 there’s going to be a Meet Magento World, taking place in, well – anywhere and everywhere actually!

Over the course of three days (July 11th – 13th) more than 30 speakers will give sessions in the different conference rooms on various Magento and eCommerce topics. The sessions will be held by a long list of international speakers.

Who are the speakers and why them?

You can check out the full agenda for this 3-day event here – the speaker list has been carefully crafted out of Meet Magento veterans and those speakers who have been the best of the best – those people who got highest scores for their speeches at various local Meet Magento events around the world.

And since it’s often very rare to gather the cream of the crop in one place, Meet Magento World is a great opportunity to infuse yourself with the hottest topics presented by great speakers in a packed 3 days of events.

One speaker’s perspective – an interview with Ivona

Now, we are very proud to have one of our own make the cut – Ivona Namjesnik was invited to participate, so here’s her two cents (and some change) about this event.

Ivona, hi. Congratulations for being the voice of Inchoo at Meet Magento World! How does it feel to prepare for the global stage?

Aron, hi. I must admit it’s extremely flattering. Even though I believe I’m quite a “chatterbox”, I never imagined it will be part of my job description. Especially on the global level! I guess learning to channel your natural abilities sure comes in handy and makes your life a whole lot interesting.

So, let’s back up a bit. You got invited to speak at Meet Magento World mostly because of your speech at Meet Magento Czech Republic earlier this year when you got almost the perfect score – what did you talk about in Prague?

Prague was quite an adventure! I absolutely loved it there and I believe overall impression after my topic “No content marketing? No future for your eCommerce business” was really good.

I tried to explained (and if score is to be trusted, I succeeded) the role of content marketing and how it interferes with eCommerce in general. Basically, they are a match made in heaven and talk explained why.

And what about now? How will your presentation look like this time around?

I believe every brand needs a voice. And every brand has one! What brands tend to forget is a fact that being silent sends a message too.

In this talk, I’ll try to reveal the mystery of finding brand voice and following it – both on social media and when approaching customers.

ivona-mmcz-2016

In your interview before MMCZ in Prague you shared some thoughts about number of women (i.e. the lack of) speaking at Magento events, and tech events in general. We can all feel there is a slight shift taking place, but how do you feel this process can be sped up? And should it?

That’s a tough question. As a founder of a Lean In Osijek circle, I believe we as a society should do everything that’s in our power to make sure men and women are treated equally. If speeding up the process means encouraging men and women ever the same and cheering them on, yes, I’m all up for it.

For me, that’s the only normal and natural thing to do and it’s silly to think there are people who would support someone more based on their gender.

What do you think about the concept of Meet Magento World? What are the pros and cons if you compare it to a “regular” Meet Magento event?

I guess it depends on how one thinks of it. I love, and I believe delegates will love too, the fact that you can incorporate it into your working schedule easily. Meet Magento World doesn’t require travelling and you can be in something as comfy as PJ’s.

On the other hand, you miss out on mingling with the community – greatest pro of “3D” Magento events is meeting wonderful people, sharing a cup of joe and exchanging stories and experiences throughout the conference. We’ll have to think of a way to compensate for that. 🙂

Since this event is something really new in the Magento ecosystem, can you put on a fortune teller hat (as opposed to the storyteller one you wear on any given weekday) and tell us how do you see this event play out? Epic or fail?

Hahah, I never really like those hats. But, I see a lot of effort being put into this and amazing colleagues from the Magento community participating – you can only imagine my predictions. 😉

So epic it is. Thanks for your time, Ivona. Any final thoughts to people thinking about becoming a part of Meet Magento World?

I would usually say “See you at the conference” but given the fact it’s all about digital world and connections now – see you on social media.

So there you have it. The thoughts of one of the Meet Magento World speakers.

NOTE: Meet Magento Association has decided to move the conference from December 2016 which was originally planned to July 2017. You can read about the reasons for this change in this article – Meet Magento World moved to July

Stay tuned and check out Meet Magento World official website and join the conversation #MM16WORLD and #MM17WORLD hashtags on Twitter for the latest news – and see you all at Meet Magento World!

The post Meet Magento World brings Magento experts to your home and office appeared first on Inchoo.

]]>
http://inchoo.net/magento/meet-magento-world-2016-online-conference/feed/ 1
Your Magento shop deserves a custom tailored theme! http://inchoo.net/magento/custom-magento-theme/ http://inchoo.net/magento/custom-magento-theme/#comments Fri, 28 Oct 2016 10:40:18 +0000 http://inchoo.net/?p=27953 Getting your store up is a process. One which starts with a revelation that, if you want to make things work in the long run, you’ll need a webshop. An online place for reaching new and informing existing customers. For them, it’s a brand new channel where they can buy your products. For you, it’s...

The post Your Magento shop deserves a custom tailored theme! appeared first on Inchoo.

]]>
Getting your store up is a process. One which starts with a revelation that, if you want to make things work in the long run, you’ll need a webshop. An online place for reaching new and informing existing customers. For them, it’s a brand new channel where they can buy your products. For you, it’s a strategic decision. Why? Because the idea behind that action is growing your business.

But, apart from choosing the platform or the hosting, you should also think about the visual appeal of your store. In this interview, Marko Briševac, one of Inchoo designers, answers the most frequent questions we get from prospective clients. Read more to find out what’s it all about!

Most of our clients contemplate getting an existing theme template rather than deciding on investing in getting one which is custom made. Why do you think it is extremely important to invest in a theme tailored according to their needs?

Marko: I must admit we get this question a lot. After talking with a lot of clients and fellow designers throughout the years, here’s the best possible answer based on my experience. 

Design process is a series of steps through which designers come up with a solution to a problem. Even though clients think it would be nice to skip all (or some of) those steps and buy a nice looking theme, every step must be completed to ensure a successful project. Design should promote improving the quality of the user’s interaction with a store and perception of a brand. None of these can be achieved with a pre-built theme.

Pre-built themes rarely care for usability, performance or user experience. Authors of these themes are aware of the fact that people are naturally more attracted to the things which are aesthetically pleasing, especially if they come with a whole list of features. However, that doesn’t necessarily mean they are solving a problem for the brand, communicating with customers in the right way or ensuring amazing user experience.

Pre-built theme could work if you are a small business owner with limited funds looking for a simple website as a space to provide general information about your company, but it should not be an option for a thriving eCommerce business.

design-sketching

How does “I want a site that looks exactly like example.com” fit into the picture? Or, better yet, why it doesn’t fit into it?

Marko: There are two different types of clients when it comes to this.

Some clients refer only to visual direction when they mention they like a certain site. For example, 70% of all clients will say they like Apple web site. Solution, in this case, isn’t to copy everything from Apple, but to recognise that the client likes effective use of white space, minimalistic colour scheme, large high quality images, etc. It is necessary to perceive are there some obvious elements on example sites which client likes but doesn’t know how to describe.

Other clients want to copy literally everything – style, features, behaviour, functionalities… I have to say it’s completely understandable why they want to copy from more successful competitors. They simply believe they will achieve the same results. For them, successful competitors’ website is a pattern of how things should be done in their field.

If that’s the case, it is crucial to explain that their business and brand is unique in every way, and what works for one doesn’t for the other even though they may be in the same industry.

We’d like to point out that our job is to carefully consider both the needs of the client and the needs of the user so we can maximize the results. We can achieve that only by planning, research and testing, definitely not by copying from others.

design-theme

Design process is what people usually connect with something being “pretty” when, in fact, it requires a lot of hard work. Can you describe, briefly, what goes into making a custom theme and benefits it yields?

Marko: At Inchoo, our design process consists of several phases through which we emphasise, define, ideate, prototype and test.

Here I’ll just mention that during that process we research, report detected problems and suggest improvements. Our design process focuses on users and their needs and business goals of the client in order to create cohesion, optimize conversion rates and find sustainable growth.

If you’re interested in all of our “behind the scenes” work, you can check out this article which covers every step we go through while working on your custom theme.

Main conclusion is definitely that design today should be (and at Inchoo is) way more than creating a visually pleasing site.

As Marko already pointed out, design is way more than something being pretty. If you’re thinking on whether you should invest in a custom tailored theme for your Magento shop, keep in mind that your customers judge you based on it. The more you understand them and display that through your shop, the more they’re likely to understand – and buy – from you.

What is your experience with the design? In case you need practical, customised pointers on what to improve on your store, we can offer you our usability audit service – we’d like to help you out so feel free to get in touch!

The post Your Magento shop deserves a custom tailored theme! appeared first on Inchoo.

]]>
http://inchoo.net/magento/custom-magento-theme/feed/ 3