How to set up multiple Magento 2 websites / stores with Nginx?

September 21, 2016  |  1 Comments  |  by Raj (MagePsycho)  |  Magento 2, Nginx

Magento is so flexible that one instance of it can have multiple websites/stores with different languages, domain names, categories etc.
You can configure the websites, stores, and store views in the Magento Admin. You use the MAGE_RUN_TYPE and MAGE_RUN_CODE variables in entry point scripts(index.php), .htaccess or Nginx configuration files(depending upon the web server you are using) to start the Magento application using these websites or store views.

Usage of MAGE_RUN_TYPE and MAGE_RUN_CODE

The code (i.e. MAGE_RUN_CODE and MAGE_RUN_TYPE) checks two environmental variables

$_SERVER['MAGE_RUN_TYPE']
$_SERVER['MAGE_RUN_CODE']

and use them to start Magento with the right website/store which can be defined in Manage Stores section in Admin.

The value of MAGE_RUN_TYPE determines if MAGE_RUN_CODE should be considered the code of a website or a store.

  • If MAGE_RUN_TYPE = ‘website’ is used, MAGE_RUN_CODE should be the code of the website and default store for this website will be loaded in the frontend.
  • If MAGE_RUN_TYPE = ‘store’ is used, MAGE_RUN_CODE should be the code of any store view and that particular store will be loaded in the frontend.

Nginx Configuration Settings

As mentioned before, there are many ways to configure the MAGE_RUN_TYPE and MAGE_RUN_CODE environment variables. But setting environment variables using web servers would be the best way as it doesn’t involve any core code edits.
Since I am using MEMP Stack, I will be sharing on how to configure Nginx for multi-website/store environment.

Case 1: One Website, Multiple Store Views

For example, we have

  • mystore.com (store code: mystore_en)
  • mystore.de (store code: mystore_de)
  • mystore.es (store code: mystore_es)

Step 1. Edit your Nginx virtual host configuration file as

File: Usually located under /etc/nginx/sites-available/ or /usr/local/etc/nginx/sites-available/ or other depending upon the OS type.

map $http_host $MAGE_RUN_CODE {
    mystore.com mystore_en;
    mystore.de mystore_de;
    mystore.es mystore_es;
}
server {
    listen 80;
    server_name mystore.com mystore.de mystore.es;
    set $MAGE_ROOT /path/to/your/magento2;
    set $MAGE_MODE default;
    include /path/to/your/magento2/nginx.conf.sample;
}

Here you can see how the Nginx map block is used to set MAGE_RUN_CODE as per host. And server_name directive includes all the available domains.

Step 2. Send MAGE_RUN_CODE and MAGE_RUN_TYPE variables to the php-fpm server
File: include path from above, example: path/to/your/magento2/nginx.conf.sample

#...
# PHP entry point for main application
location ~ (index|get|static|report|404|503)\.php$ {
    try_files $uri =404;
    fastcgi_pass   fastcgi_backend;
    fastcgi_buffers 1024 4k;

    fastcgi_param  PHP_FLAG  "session.auto_start=off \n suhosin.session.cryptua=off";
    fastcgi_param  PHP_VALUE "memory_limit=768M \n max_execution_time=600";
    fastcgi_read_timeout 600s;
    fastcgi_connect_timeout 600s;
    
    #add here - start
    fastcgi_param  MAGE_RUN_TYPE store;
    fastcgi_param  MAGE_RUN_CODE $MAGE_RUN_CODE;
    #end

    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}
#...

Here Nginx fastcgi_param‘s MAGE_RUN_TYPE & MAGE_RUN_CODE will create the enviroment varilables for PHP, so that the script can access the values via

$_SERVER['MAGE_RUN_TYPE']
$_SERVER['MAGE_RUN_CODE']

Case 2: Multiple Website, Multiple Store views

For example, we have

  • mystore1.com (website code: mystore1)
    • mystore1.com (store code: mystore1_en)
    • mystore1.de (store code: mystore1_de)
    • mystore1.es (store code: mystore1_es)
  • mystore2.com (website code: mystore2)
    • mystore2.com (store code: mystore2_en)
    • mystore2.de (store code: mystore2_de)
    • mystore2.es (store code: mystore2_es)

In order to configure for this case, you can use the similar settings as for Case 1. The only difference will be the store code values for Nginx map block and domain names for server_name directive.

Conclusion

If you are unsure about which value(website or store) should you use for MAGE_RUN_TYPE, go for the store value and use the corresponding store code for MAGE_RUN_CODE

Hope this helps you in configuring the Nginx for multi-store/website. In the case of any issues with the multi-store setup, please do comment below.

Sandbox script for quick testing in Magento2

September 14, 2016  |  No Comments  |  by Raj (MagePsycho)  |  Linux, Mac OS-X, Magento 2

What’s your approach for quick & dirty testing in Magento2?

  • Creating a test module with a controller and executing it from browser to see the output?
  • Creating a Console module and executing via CLI?

Obviously, above two approaches takes time.

Rather I would create a simple script (a single file) and put it somewhere in /[path-to-magento2]/pub/sandbox.php

<?php
/**
 * @author Raj KB<magepsycho@gmail.com>
 * @website http://www.magepsycho.com
 */
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require __DIR__ . '/../app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
$obj = $bootstrap->getObjectManager();
$state = $obj->get('Magento\Framework\App\State');
$state->setAreaCode('frontend');

// Your quick and dirty code goes here...
$quote = $obj->get('Magento\Checkout\Model\Session')->getQuote()->load(1);
Zend_Debug::dump($quote->getOrigData());

Now you can easily test by pointing to URL
http://[magento2-url]/sandbox.php

But wait, this won’t work in the case when you are using Nginx + PHP-FPM server.
The reason you can explore from the Nginx conf file: [path/to/magento2]/nginx.conf.sample

...
# PHP entry point for main application
location ~ (index|get|static|report|404|503)\.php$ {
    try_files $uri =404;
    fastcgi_pass   fastcgi_backend;
    fastcgi_buffers 1024 4k;

    fastcgi_param  PHP_FLAG  "session.auto_start=off \n suhosin.session.cryptua=off";
    fastcgi_param  PHP_VALUE "memory_limit=768M \n max_execution_time=600";
    fastcgi_read_timeout 600s;
    fastcgi_connect_timeout 600s;

    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include        fastcgi_params;
}
...

As you can see only PHP files: index.php, get.php, static.php, report.php, 404.php & 503.php files are parsed by the PHP-FPM.

In order to include your sandbox.php in the whitelist, just edit the line of Nginx conf file:

location ~ (index|get|static|report|404|503)\.php$ {

to

location ~ (index|get|static|report|404|503|sandbox)\.php$ {

And don’t forget to reload or restart your Nginx server depending upon your Operating System.
Ubuntu

sudo service nginx reload
sudo service nginx restart

MacOSx

sudo nginx -s reload
sudo nginx -s stop && sudo nginx

What’s your approach for quick & dirty testing in Magento2? Please do comment below.

Magento 2: How the quantity_and_stock_status attribute value is populated in product object?

August 6, 2016  |  No Comments  |  by Raj (MagePsycho)  |  Magento 2

Well, a new product attribute that you may notice in Magento2 is quantity_and_stock_status.

SELECT * FROM eav_attribute WHERE attribute_code = 'quantity_and_stock_status';

Query Result:
quantity_and_stock_status attribute info

If you load a product object as:

$product = $this->_productRepository->getById($productId);
var_dump($product->getData());

You will see the value of quantity_and_stock_status as an array of is_in_stock and qty.
You must be thinking from where the heck it came from? These values are supposed to be in the stock item object.

Let’s dig it out.
Try to load any product:

$product = $this->_productRepository->getById($productId);

Inspect the value of following using X-Debug

$product->getData('quantity_and_stock_status')

You will notice that the value of quantity_and_stock_status is 1 initially (i.e. the default value).
Once the product is loaded, afterLoad() method of attribute’s backend_model: Magento\Catalog\Model\Product\Attribute\Backend\Stock is executed.

# Class: \Magento\Catalog\Model\Product\Attribute\Backend\Stock
/**
 * Set inventory data to custom attribute
 *
 * @param Product $object
 * @return $this
 */
public function afterLoad($object)
{
    $stockItem = $this->stockRegistry->getStockItem($object->getId(), $object->getStore()->getWebsiteId());
    $object->setData(
        $this->getAttribute()->getAttributeCode(),
        ['is_in_stock' => $stockItem->getIsInStock(), 'qty' => $stockItem->getQty()]
    );
    return parent::afterLoad($object);
}

From above you can see clearly that is_in_stock and qty value are being fed by the $stockItem object.
And hence scalar value becomes an array.
Now it’s no more mystery, right? Cool.

Now let’s comment on – “Why has Magento2 team introduced quantity_and_stock_status attribute?”

MySQL Issue: Table storage engine for ‘catalog_product_relation’ doesn’t have this option

July 30, 2016  |  No Comments  |  by Raj (MagePsycho)  |  Linux, Mac OS-X, Magento, Mysql

When you export the Magento database dump from MySQL 5.5.x and try to import in MySQL 5.7.x, You are likely to face an error:

ERROR 1031 (HY000) at line 3002: Table storage engine for ‘catalog_product_relation’ doesn’t have this option

PROBLEM

This is probably due to the table option that you have in your CREATE TABLE DDL: ROW_FORMAT=FIXED

Let’s check if there is any such string in the SQL dump (Ex: magento-db-dump.sql).

cat magento-db-dump.sql | grep '=FIXED'

Which resulted as:

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Catalog Product Relation Table';
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=FIXED COMMENT='Catalog Product To Website Linkage Table';

Refer – MySQL Row Format Option

SOLUTION

Removing ROW_FORMAT=FIXED option from CREATE TABLE DDL will fix the issue.
So let’s try possible solutions.

#1

sed -i 's/ROW_FORMAT=FIXED//g' magento-db-dump.sql

This didn’t work for me in MacOSx which resulted in following error:

sed: 1: “magento-db-dump.sql”: invalid command code m

#2

sed -i '' 's/ROW_FORMAT=FIXED//g' magento-db-dump.sql

And even this resulted as:

sed: RE error: illegal byte sequence

#3
But this one worked for me in MacOSx (Refer Mac OS X, Sed, and strange document encoding to know more about the issue)

LC_ALL=C sed -i '' 's/ROW_FORMAT=FIXED//g' magento-db-dump.sql

Re-check if the string has been removed or not:

cat magento-db-dump.sql | grep '=FIXED'

If the string is removed, now try to import as:

mysql -u <user> -p <db-name> < magento-db-dump.sql

Yay! Now it imported successfully.
Please do share and care if you liked this article.

Cheers!

Configure your Bash Shell for Magento 2 CLI Commands

June 29, 2016  |  No Comments  |  by Raj (MagePsycho)  |  Mac OS-X, Magento 2

One of the major improvement in Magento 2 is the introduction of CLI commands based on Symfony Console Component.

CLI commands can perform multiple tasks. Some of them are:

  • Installing Magento (and related tasks such as creating or updating the database schema, creating the deployment configuration, and so on)
  • Clearing the cache
  • Managing indexes, including reindexing
  • Creating translation dictionaries and translation packages
  • Generating non-existent classes such as factories and interceptors for plug-ins, generating the dependency injection configuration for the object manager.
  • Deploying static view files
  • Creating CSS from LESS
  • Providing “plug in” capability for third party developers

To invoke the CLI command:

php /path/to/your/magento2/bin/magento group:[subject:]action

To list all the available console commands:

php /path/to/your/magento2/bin/magento --list

which will output similar to:
Magento2 CLI Commands

In this article, we will discuss on how to configure bash shell for Magento 2 CLI to increase your productivity.

Bash Configuration Tips for Magento 2 Console

1. Install Bash Completion

Refer to our previous article – Install Bash Completion for Mac OS-X, which shows how to enable the completion feature for shell commands just by pressing [TAB]

2. Create useful bash aliases

Alias for bin/magento: mage2
Edit your ~/.bash_profile and add the following code:

alias mage2 = "./bin/magento"

Now instead of using:

php bin/magento [options]

You can simply use:

mage2 [options]

(Must be run from root of Magento2 path)

Alias for enabling module: mage2-module-enable
In order to enable module in Magento2, you have to run following series of command:

php bin/magento module:enable Vendor1_Module1 VendorN_ModuleN --clear-static-content
php bin/magento setup:upgrade

To simplify this process, you can simply add the alias as:

mage2ModuleEnable() {
	mage2 module:enable "$@" --clear-static-content && mage2 setup:upgrade
}
alias mage2-module-enable="mage2ModuleEnable"

Now you can simply enable the module(s) by running:

mage2-module-enable Vendor1_Module1 Vendor2_Module2

These are just a few examples. You can add similarly if needed.

Isn’t that a smarter way of executing CLI command?
Note: Don’t forget to reload the bash after adding aliases:

source ~/.bash_profile

3. Install Magento2 Bash Completion

Some of the cool dudes in Magento community are really doing amazing work. One of such work is magento2-bash-completion plugin.

In order to install this magento2-bash-completion, proceed as follow.
Download plugin to bash-completion hook folder

curl -o /usr/local/etc/bash_completion.d/magento2-bash-completion https://raw.githubusercontent.com/yvoronoy/magento2-bash-completion/master/magento2-bash-completion

Reload the shell

source /usr/local/etc/bash_completion.d/magento2-bash-completion

Time for Magic
Just write few letters of command and press [TAB], you will see as per the following gif:
Magento2 Bash Completion
(Image Source: https://github.com/yvoronoy/magento2-bash-completion)

Do you have any useful tips on executing Magento2 CLI commands? Please do share in the comment.
(We will keep on updating this post with more cool bash tips for Magento 2 CLI, so keep your eye on it.)