Subjects

I am pretending that you do not need to implement an entirely new functionality where a plug-in makes sense, e.g. your want to invoke a method of the Magento RESTFul API after the product import has been finished; in most cases, a combination of one or more subject(s) with some observers can fully cover the requirements of a new use case.

This chapter will discuss when it makes sense to implement a subject and how it could be done.

When do I need a subject?

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

These plug-ins allow you to register an unlimited number of subjects, whereas each of them can have an unlimited number of observers.

In case a new file has to be imported is found, the TechDivision\Import\Plugins\SubjectPlugin invokes the process() method of all registered subjects on this file in the order they have been registered.

The topic itself processes the file’s content by invoking ALL registered observers for each row of the given file.

You can imagine this as something like a chain that can be configured by a workflow engine. This approach allows you to process nearly every file by reading the file contents row by row.

So, usually, you need to implement a subject when

  • You want to process an import file, independent which format it has

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

  • You need to customize the file parsing, e.g. not parsing a single row, but a bunch of rows

In general, you need a subject when you want to do something with a file.

How to implement a subject?

In general, you should consider extending TechDivision\Import\Subjects\AbstractSubject or one of its subclasses, e.g. TechDivision\Import\Subjects\AbstractEavSubject if you want to implement the import for another EAV entity.

It would at least help if you implemented the interface TechDivision\Import\Subjects\SubjectInterface which is the minimum requirement for a subject implementation.

Let’s implement a standard requirement, where you need a subject that allows one of its observers to load a product with the SKU found in the import file, adding the SKU to entity_id mapping to the subject and finally pass the mappings to the next subject.

namespace TechDivision\Import\Product\Subjects;

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

class MySubject extends AbstractSubject
{

    /**
     * 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)
    {

        // 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)
    {

        // 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)
    {
        $this->skuEntityIdMapping[$sku] = $entityId;
    }
}

The subject provides the addSkuEntityIdMappping() method, which will be invoked by the observer with the ID import_product.observer.my.observer that loads the product by the SKU found in the import file.

Additionally, it implements the methods setUp() and tearDown(), that’ll be invoked automatically before and after the import file has been processed.

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

To make your subject accessible for the Workflow Engine, you first have to define it in the Symfony DI configuration file symfony/Resources/config/services.xml of your component.

Depending on your namespace, this would look like

<?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">
            <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">
            <argument type="service" id="import_product.processor.product.bunch"/>
        </service>
    </services>
</container>

The subject from above can finally be added to the Workflow Engine with the following configuration.

{
    "id": "import.plugin.subject",
    "subjects": [
      {
          "id": "import_product.subject.my",
          "identifier": "files",
            "file-resolver": {
              "prefix": "product-import"
            },
          "observers": [
            "import_product.observer.my"
          ]
      }
    ]
}

Instead we will implement the observer with the ID import_product.observer.my.observer in the next chapter regarding Observers .