Tag Archive magento 2

Magento 2 : create new stores using a store create processor

I have previously already created a post with a code example that show how to create stores programmaticaly. Here is this previous post : http://www.berliozd.com/magento-2-create-a-store-storegroup-website-programmaticaly

Here I present an other way of doing if using a native create processor present in Magento_Store module.

We are going to use a helper that contains the logic for creating new stores.

This helper holds the native processor. It passes a data table to it and execute its run method.

<?php
/**
 * @author Didier Berlioz
 * Copyright (c) Addeos All rights reserved.
 */
 
namespace Addeos\Store\Helper;

use Exception;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Store\Api\StoreRepositoryInterface;
use Magento\Store\Model\Config\Importer\Processor\Create;
use Magento\Store\Model\ResourceModel\Store as StoreModel;
use Psr\Log\LoggerInterface;

class Store extends AbstractHelper
{

    /**
     * @var Create
     */
    private $storeCreateProcessor;

    /**
     * @var StoreRepositoryInterface
     */
    private $storeRepository;

    /**
     * @var StoreModel
     */
    private $storeResourceModel;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var ConfigInterface
     */
    private $config;

    /**
     * Store constructor.
     * @param Context $context
     * @param Create $storeCreateProcessor
     * @param StoreRepositoryInterface $storeRepository
     * @param StoreModel $storeResourceModel
     * @param LoggerInterface $logger
     */
    public function __construct(
        Context $context,
        Create $storeCreateProcessor,
        StoreRepositoryInterface $storeRepository,
        StoreModel $storeResourceModel,
        LoggerInterface $logger
    ) {
        parent::__construct($context);
        $this->storeCreateProcessor = $storeCreateProcessor;
        $this->storeRepository = $storeRepository;
        $this->storeResourceModel = $storeResourceModel;
        $this->logger = $logger;
    }

    public function createStores($data)
    {
        try {
            $this->storeCreateProcessor->run($data);
            foreach ($data['stores'] as $storeData) {
                $this->updateStore($storeData['code'], $storeData['group_id'], $storeData['website_id']);
            }
        } catch (Exception $e) {
            $this->logger->error(__FILE__ . ' : ' . $e->getMessage());
        }
    }
    
    private function updateStore($code, $groupId, $websiteId): void
    {
        try {
            $store = $this->storeRepository->get($code);
            $store->setStoreGroupId($groupId);
            $store->setWebsiteId($websiteId);
            $this->storeResourceModel->save($store);
        } catch (Exception $e) {
            $this->logger->error(__FILE__ . ' : ' . $e->getMessage());
        }
    }


And then an installer that will use that helper. What this installer does is basically building the data and passing it to the helper.

In that example, we are creating 2 new websites which each have a group and a store.

<?php
/**
 * @author Didier Berlioz
 * Copyright (c) Addeos All rights reserved.
 */

namespace Addeos\Store\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Addeos\Store\Helper\Store;

class InstallData implements InstallDataInterface
{
    const FIRST_STORE_ID = 8;
    const SECOND_STORE_ID = 9;
    const FIRST_WEBSITE_ID = 6;
    const SECOND_WEBSITE_ID = 7;
    const FIRST_GROUP_ID = 6;
    const SECOND_GROUP_ID = 7;
    const FIRST_STORE_COUNTRY = 'DE';
    const SECOND_STORE_COUNTRY = 'AT';
    const FIRST_STORE_LOCALE = 'de_DE';
    const SECOND_STORE_LOCALE = 'at_DE';
    const ROOT_CATEGORY_ID = 2;
    
    /**
     * @var Store
     */
    private $storeHelper;

    /**
     * InstallData constructor.
     * @param Store $storeHelper
     */
    public function __construct(Store $storeHelper)
    {
        $this->storeHelper = $storeHelper;
    }

    /**
     * @inheritDoc
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $this->storeHelper->createStores($this->getMyStoresData());
    }

    /**
     * @return array
     */
    private function getMyStoresData(): array
    {
        return [
            'websites' => [
                [
                    'website_id' => self::FIRST_WEBSITE_ID,
                    'code' => 'first_website_code',
                    'name' => 'First website name',
                    'sort_order' => 4,
                    'default_group_id' => self::FIRST_GROUP_ID,
                    'is_default' => 0,
                ],
                [
                    'website_id' => self::SECOND_WEBSITE_ID,
                    'code' => 'second_website_code',
                    'name' => 'Second website name',
                    'sort_order' => 5,
                    'default_group_id' => self::SECOND_GROUP_ID,
                    'is_default' => 0,
                ],
            ],
            'groups' => [
                [
                    'group_id' => self::FIRST_GROUP_ID,
                    'website_id' => self::FIRST_WEBSITE_ID,
                    'code' => 'first_group_code',
                    'name' => 'First group name',
                    'root_category_id' => self::ROOT_CATEGORY_ID,
                    'default_store_id' => self::FIRST_STORE_ID,
                ],
                [
                    'group_id' => self::SECOND_GROUP_ID,
                    'website_id' => self::SECOND_WEBSITE_ID,
                    'code' => 'second_group_code',
                    'name' => 'Second group name',
                    'root_category_id' => self::ROOT_CATEGORY_ID,
                    'default_store_id' => self::SECOND_STORE_ID,
                ],
            ],
            'stores' => [
                [
                    'store_id' => self::FIRST_STORE_ID,
                    'code' => 'first-store-code',
                    'website_id' => self::FIRST_WEBSITE_ID,
                    'group_id' => self::FIRST_GROUP_ID,
                    'name' => 'First store name',
                    'sort_order' => 0,
                    'is_active' => 0,
                    'locale' => self::FIRST_STORE_LOCALE,
                    'country' => self::FIRST_STORE_COUNTRY,
                ],
                [
                    'store_id' => self::SECOND_STORE_ID,
                    'code' => 'second-store-code',
                    'website_id' => self::SECOND_WEBSITE_ID,
                    'group_id' => self::SECOND_GROUP_ID,
                    'name' => 'Second store name',
                    'sort_order' => 0,
                    'is_active' => 0,
                    'locale' => self::SECOND_STORE_LOCALE,
                    'country' => self::SECOND_STORE_COUNTRY,
                ],
            ],
        ];
    }
}

Magento 2 : Database anonymization module

This is my first module deployed on packagist.

This module once installed will offer a new command for anonymizing the database.

This can be useful on a development environment when the database has been retrieved from a production environment. It will anonymize all the customer’s data (replace personal data) to be compatible on a GDPR point of view.

Install the module using composer :

composer require addeos/anonymize

Once the module is installed on your magento, simply call it using the following command :

php bin/magento addeos:anonymize

After execution is done, all your customer personal data will be transformed.

This must not be executed on a production environment!

Sometimes, some magento application can be set on production application mode even if the magento is not a production application. This can be on a preproduction magento for example.

In this case, there is a safety that will prevent the command to work but you can still call the command with a -f 1 option

php bin/magento addeos:anonymize -f 1

Magento 2 : How to add a knockout component

We can add a knockout component to add some javascript behaviour in our pages. This is how to do it step by step.

1 . In the existing template, in which you want to include the knockout component, you first need to declare the component using the declarative notation and using the <script type="text/x-magento-init" /> tag.

This allows you to specify the component, basically the js file and the associated template.

<script type="text/x-magento-init">
    {
        "#demo-component-container": {
            "Magento_Ui/js/core/app": {
               "components": {
                    "demo-ko-component": {
                        "component" : "MyNamespace_MyModule/js/ko-component",
                        "config" : {
                            "template":  "MyNamespace_MyModule/ko-template"
                        }
                    }
                }
            }
        }
    }
</script>

2. In the same template, you then need to declare the container that will receive the component.

<div id="demo-component-container" data-bind="scope: 'demo-ko-component'">
    <!-- ko template: getTemplate() --><!-- /ko -->
</div>

3. You need to create the js component.

It has to be place in app/code/MyNamespace/MyModule/view/frontend/web/js/ko-component.js.

For the purpose of the example, we are simply setting a property inputValue that we will use in the template.

/**
 * @license http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 * @author Didier Berlioz <berliozd@gmail.com>
 * @copyright Copyright (c) 2016 Addeos (http://www.addeos.com)
 */

define(['jquery', 'uiComponent', 'ko'], function ($, Component, ko) {
        'use strict';
        return Component.extend({
            initialize: function () {
                this._super();
                this.inputValue = ko.observable('my input value');
            }
        });
    }
);

4. You need to create the component template.

It will be placed here app/code/Namespace/Module/view/frontend/web/template/ko-template.html .

As said earlier, in the template we will just bind an input tag with the property inputValue set in the js component.

<p>Here is my knockout template.</p>
<input type="text" data-bind="value: inputValue">

And that’s all.

Magento 2 : How to create a store, a store group and a website programmatically

Whenever it comes during your projects, you can need to create a new store, a new store group and a new website. You can do it programmatically.

A store refers a store group to which “it is part” and a store group refers a website to “which it is part”. But a website also refers a default store group and and store group refers a default store.

You then need to decide what entity to create first, refer to other entities and then change the references after having created the final entities. The solution I have decided to use is to create the website, then the store group and finally the store.

The following code have to be place in an installer file (InstallData.php or UpgradeData.php placed in the Setup folder of a module. The class have to implements either \Magento\Framework\Setup\UpgradeDataInterface or \Magento\Framework\Setup\InstallDataInterface.

<?php

namespace Addeos\Core\Setup;

use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Store\Model\ResourceModel\Group as GroupResourceModel;
use Magento\Store\Model\ResourceModel\Store as StoreResourceModel;
use Magento\Store\Model\ResourceModel\Website as WebsiteResourceModel;
use Magento\Store\Model\GroupFactory;
use Magento\Store\Model\StoreFactory;
use Magento\Store\Model\WebsiteFactory;

class UpgradeData implements UpgradeDataInterface
{
    
    /**
     * UpgradeData constructor.
     * @param Installer $installer
     * @param ManagerInterface $eventManager
     * @param WebsiteResourceModel $websiteModel
     * @param GroupResourceModel $groupModel
     * @param StoreResourceModel $storeModel
     * @param WebsiteFactory $websiteFactory
     * @param GroupFactory $groupFactory
     * @param StoreFactory $storeFactory
     */
    public function __construct(
        Installer $installer,
        ManagerInterface $eventManager,
        WebsiteResourceModel $websiteModel,
        GroupResourceModel $groupModel,
        StoreResourceModel $storeModel,
        WebsiteFactory $websiteFactory,
        GroupFactory $groupFactory,
        StoreFactory $storeFactory,
    ) {
        $this->installer = $installer;
        $this->eventManager = $eventManager;
        $this->websiteResourceModel = $websiteModel;
        $this->groupResourceModel = $groupModel;
        $this->storeResourceModel = $storeModel;
        $this->websiteFactory = $websiteFactory;
        $this->groupFactory = $groupFactory;
        $this->storeFactory = $storeFactory;
    }
    
    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        if (version_compare($context->getVersion(), '1.0.4', '<')) {
            $this->createEntities();
        }    
    }
    
    private function createEntities()
    {
        /** @var Magento\Store\Model\Website $website */
        $website = $this->websiteFactory->create();
        $website->setCode('website_code');
        $website->setName('website name');
        // Set an existing store group id
        $website->setDefaultGroupId(2);
        $this->websiteResourceModel->save($website);

        /** @var \Magento\Store\Model\Group $group */
        $group = $this->groupFactory->create();
        $group->setWebsiteId($website->getWebsiteId());
        $group->setName('store group name');
        // Set the category id for the root category of the store group
        $group->setRootCategoryId(2);
        // Set an existing store id
        $group->setDefaultStoreId(3); 
        $this->groupResourceModel->save($group);

        /** @var \Magento\Store\Model\Store $store */
        $store = $this->storeFactory->create();
        $store->setCode('store_code');
        $store->setName('store name');
        $store->setWebsite($website);
        $store->setGroupId($group->getId());
        $store->setData('is_active', '1');
        $this->storeResourceModel->save($store);
        $this->eventManager->dispatch('store_add', ['store' => $store]);

        // change the default group id for the website
        $website->setDefaultGroupId($group->getId());
        $this->websiteResourceModel->save($website);

        // change the default store id for the store group
        $group->setDefaultStoreId($store->getId());
        $this->groupResourceModel->save($group);
    }

}

Magento 2 : How to add and update a database table column

It’s often necessary to update database tables when working on module. We can need to add columns or update some of them.

The following codes have to be in the installer files of the module you are working in. They can be either InstallSchema.php or UpgradeSchema.php. These files have to be placed in the Setup folder of the module.

In the following code example, we are adding a column to an existing table. We assume we are working on a newly created module that hasn’t been installed yet. Therefore we are working on the InstallSchema.php file. The implemented function is “install”.

<?php

namespace Addeos\MyModule\Setup;

use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\InstallSchemaInterface;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, 
        ModuleContextInterface $context)
    {
        $installer->getConnection()
            ->addColumn($installer->getTable('my_table_name'),
                'my_column_name',
                [
                    'type' => Table::TYPE_SMALLINT,
                    'nullable' => false,
                    'comment' => 'Write here a comment that will 
                        be visible in when looking at the table info in mysql'
                ]);
    }
}

Here we are updating a column in the same table. We are then upgrading the module which has already been installed. Therefore, we are in the UpdgradeSchema.php file. We assume this new version will be 1.0.1. The implemented function is “upgrade” this time.

<?php

namespace Addeos\MyModule\Setup;

use Magento\Framework\DB\Ddl\Table;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\UpgradeSchemaInterface;

class UpgradeData implements UpgradeDataInterface
{
    public function upgrade(SchemaSetupInterface $setup, 
        ModuleContextInterface $context)
    {
        if (version_compare($context->getVersion(), '1.0.1', '<')) {
            $setup->getConnection()
                ->changeColumn(
                    $installer->getTable('my_table_name'),
                    'my_column_name',
                    'my_column_name', [
                        'type' => Table::TYPE_SMALLINT,
                        'nullable' => true,
                        'comment' => 'You still have to write again the comment 
                            otherwise it be lost.'
                    ]
                );
        }
        
    }
}

Magento 2 : How to add a logging function when debugging

When debugging, it might be necessary to add some logs.
Here is a simple way to add a logging function in your code.
Warning :
This is not supposed to stay in your code and is only for debugging purpose.
There are better and more integrated ways of doing it

<?php
/**
* @param $str
*/ 
private function log($str) { 
    $str = 'CLASS : ' . str_pad(__CLASS__, 50, ' ')
            . ' - LINE : ' . str_pad(debug_backtrace()[0]['line'], 4, ' ')
            . ' - FUNCTION : ' . str_pad(debug_backtrace()[1]['function'] , 15, ' ')
            . ' - STR : ' . $str;
    $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); 
    /** @var \Magento\Framework\Filesystem\DirectoryList $directory */ 
    $directory = $objectManager->get('\Magento\Framework\Filesystem\DirectoryList');
    $rootPath = $directory->getPath(\Magento\Framework\App\Filesystem\DirectoryList::VAR_DIR); 
    $logger = new \Zend\Log\Logger();
    $writer = new \Zend\Log\Writer\Stream($rootPath . '/log/zendlog.log'); 
    $logger->addWriter($writer);
    $logger->debug($str); 
}

Magento 2 : How to add a jquery widget

When you need to add javascript in your magento 2, one solution is to implement a jQuery widget.
It’s pretty simple and straight forward.
This is how to do it step by step.

1. In the phtml template, your need to specify the component that will be available for the dom element :

<script type="text/x-magento-init">
{
  “#dom-id": {
    “componentName": {}
  }
}
</script>

Here componentName will be available for #dom-id

2. Then add the require js config
In <namespace>/<module>/view/frontend/requirejs-config.js add :

var config = {
map: {
    '*': {
      componentName: 'Namespace_Module/js/component'
    }
  }
};

Here you specify that the component componentName will map <namespace>/<module>/js/component.
In other words it will map : /app/code/<namespace>/<module>/view/frontend/web/js/component.js

3. Then create the jQuery widget itself :

In /<namespace>/<module>/view/frontend/web/js/component.js
Add the following code :

define(['jquery','moment'], function ($, moment) {
      'use strict';

      $.widget('<namespace>.<module>', {
         _create: function () {
            Console.log(‘boo’);
          }
     });
     return $.<namespace>.<module>;
});

And that’s it.