Subjects

In most cases it is not necessary to implement entirely new functionality.

A combination of one or more subjects with observers can completely cover the requirements of a new use case for a new custom plugin.

When do I need a subject?

When is a separate plugin helpful, and when does it make sense to implement and use a Subject?::

  • A method of the Magento RESTFul API needs to be called after the product import gets completed

The Subject plugin TechDivision\Import\Plugins\SubjectPlugin implements the observer pattern and is the first choice for most cases.

  • The Subject plugins allow registering an unlimited number of subjects, while each of them itself can have an unlimited number of observers

  • If a new file gets found that needs to get imported, the TechDivision\Import\Plugins\SubjectPlugin calls the process() method

  • With the process() method, all registered subjects in this file get processed in the sequence; they get registered

    • It processes the contents of the files by calling all registered observers for each line of the given file

    • It is a kind of chain that a workflow engine can configure

    • This approach makes it possible to process almost any file by reading the file contents line by line

Implement a subject in the following cases:

  • You want to process an import file, regardless of its format?

  • You want to exchange data between observers or pass the data to the following subjects?

  • You need to customize the parsing of the file, e.g., parse a series of lines rather than a single line?

  • You should extend TechDivision\Import\Subjects\AbstractSubject or one of its subclasses TechDivision\Import\Subjects\AbstractEavSubject if you want to implement an import for a new EAV entity

An observer is always necessary if you intend to manipulate a line of a file

How to implement a subject?

It is helpful to implement the interface TechDivision\Import\Subjects\SubjectInterface because this is the minimum requirement for a subject implementation.

Example

Implementation of a standard requirement:
  • We need a subject that allows one of its observers to load a product with the specific SKU found in the import file, add the SKU entity_id mapping to the subject, and pass the mappings to the next subject

<?php

namespace TechDivision\Import\Product\Subjects;

use TechDivision\Import\Utils\RegistryKeys;
use TechDivision\Import\Subjects\AbstractSubject;

class MySubject extends AbstractSubject (1)
{
    /**
     * The SKU to entity_id mappings we want to pass to the next subject.
     *
     * @var array
     */
    protedted $skuEntityIdMapping = array();

    /**
     * Intializes the previously loaded global data for exactly one bunch.
     *
     * @param string $serial The serial of the actual import
     *
     * @return void
     */
    public function setUp($serial) (2)
    {
        // load the status of the actual import
        $status = $this->getRegistryProcessor()->getAttribute($serial);

        // load the SKU => entity_id mappings from further subjects
        $this->skuEntityIdMapping = $status[RegistryKeys::GLOBAL_DATA][RegistryKeys::SKU_ENTITY_ID_MAPPING];

        // invoke the parent method
        parent::setUp($serial);
    }

    /**
     * Clean up the global data after importing the bunch.
     *
     * @param string $serial The serial of the actual import
     *
     * @return void
     */
    public function tearDown($serial) (3)
    {
        // load the registry processor and add the SKU => entity_id mappings
        $this->getRegistryProcessor()->mergeAttributesRecursive(
            $serial,
            array(
                RegistryKeys::SKU_ENTITY_ID_MAPPING => $this->skuEntityIdMapping
            )
        );

        // invoke the parent method
        parent::tearDown($serial);
    }

    /**
     * Add the passed SKU => entity ID mapping.
     *
     * @param string $sku The SKU to map
     * @param integer $entityId The entity ID to be mapped
     *
     * @return void
     */
    public function addSkuEntityIdMapping($sku, $entityId) (4)
    {
        $this->skuEntityIdMapping[$sku] = $entityId;
    }
}
1 The custom class TechDivision\Import\Product\Subjects\MySubject
2 TechDivision\Import\Product\Subjects\MySubject implements the setUp() method, automatically called before and after processing the import file
3 TechDivision\Import\Product\Subjects\MySubject implements the tearDown() method, automatically called before and after processing the import file
4 The subject class TechDivision\Import\Product\Subjects\MySubject provides the method addSkuEntityIdMappping(), to get called by the observer with the ID import_product.observer.my to load the product based on the SKU found in the import file

The above methods allow us to add/load data from/to the TechDivision\Import\Services\RegistryProcessor, which acts as a data container for the entire import process, passing it from one subject to the next.

To make your subject accessible to the workflow engine afterward, you need to define it in your component’s Symfony DI configuration file symfony/Resources/config/services.xml.

Depending on your namespace, this would look like the following XML setup:
symfony/Resources/config/services.xml
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
        <service id="import_product.subject.my" class="TechDivision\Import\Product\Subjects\MySubject" shared="false"> (1)
            <argument type="service" id="import.processor.registry"/>
            <argument type="service" id="import.generator.core.config.data.uid"/>
            <argument type="service" id="loggers"/>
            <argument type="service" id="import.events.emitter"/>
        </service>
        <service id="import_product.observer.my" class="TechDivision\Import\Product\Observers\MyObserver"> (2)
            <argument type="service" id="import_product.processor.product.bunch"/>
        </service>
    </services>
</container>
1 Our defined subject ID import_product.subject.my for the custom subject class TechDivision\Import\Product\Subjects\MySubject
2 Our defined observer ID import_product.observer.my for the custom observer class TechDivision\Import\Product\Observers\MyObserver
In our next step, the just created subject can be added to the Workflow Engine with the following configuration:
{
  "id": "import.plugin.subject",
  "subjects": [
    {
      "id": "import_product.subject.my", (1)
      "identifier": "files",
      "file-resolver": {
        "prefix": "product-import"
      },
      "observers": [
        "import_product.observer.my" (2)
      ]
    }
  ]
}
1 Our configuration subject section with the ID import_product.subject.my
2 Our configuration observer section with the ID import_product.observer.my

In our next chapter about observers, we will implement an observer with the observer ID import_product.observer.my as described.