?> 2020 - Addeos

Yearly Archive 16 December 2020

Docker : various usefull commands

I am simply listing some usefull command I have been using with my docker setup locally. Some more commands will come with time.

1. How to remove exited container.

docker rm $(docker ps -a -f status=exited -q)

2. How to run a command inside a container. In the following example, we execute bash in php container.

docker-compose run php bash 

3. How to execute docker-compose using a specific docker-compose file.

docker-compose -f docker-compose.mac.yml run php bash

ElasticSearch / Magento 2 – various commands

Magento 2 uses ElasticSearch as a search engine.

It can be helpfull to directly request ElasticSearch to verify the data that have been indexed.

First you can get all indices.

In the command below ElastichSearch is accessible on host name 'elasticsearch' and on port 9200.


curl -X GET 'http://elasticsearch:9200/_cat/indices?v'

This will return the following data :

health status index                  uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   magento2_product_3_v10 h3qCDZVzT16OTOZAx3pMuA   1   1          0            0       208b           208b
yellow open   magento2_product_2_v10 uppg4DzLRSeNdf5HrgHcQg   1   1        189            0     79.4kb         79.4kb
yellow open   magento2_product_1_v10 nh8ckn7kS8-3l-F9dX8Tkw   1   1          0            0       208b           208b

You can then request documents in a specific indice.

Here for example, we are getting the 10 first documents in the indice magento2_product_2_v10.

curl -X GET --header 'Content-Type: application/json' \
http://elasticsearch:9200/magento2_product_2_v10/_search -d '{"size" : 10}'

The response look like that :

{
    "took": 3,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 189,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
          ...
        ]
    }
}

You can se that the total number of documents that is returned in the "hits" > "total" field.

A hit structure will look like that :


{
    "_index": "magento2_product_2_v10",
    "_type": "document",
    "_id": "1692",
    "_score": 1.0,
    "_source": {
        "store_id": "2",
        "options": [
            "Prod 1"
        ],
        "sku": "prod_sku_1",
        "category_ids": [
            2
        ],
        "position_category_2": "0",
        "name_category_2": "Default Category"
        }
},

You can get documents using a query. Below we are requesting the documents in category 3.


curl -XGET --header 'Content-Type: application/json' http://elasticsearch:9200/magento2_product_2_v10/_search \
-d '{"query" : {"bool": {"must": [{"term": {"category_ids": "3"}}]}}}'

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 : https://www.addeos.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