How to Create a New Connector

We’ll implement here a very minimalist Connector, it will do nothing but allow us to understand the main concepts and the overall architecture.

Create our Bundle

Create a new Symfony bundle:

1
2
3
4
5
6
7
8
9
<?php

namespace Acme\Bundle\DummyConnectorBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeDummyConnectorBundle extends Bundle
{
}

Register the bundle in AppKernel:

public function registerBundles()
{
    // ...
        new Acme\Bundle\DummyConnectorBundle\AcmeDummyConnectorBundle(),
    // ...
}

Configure our Job service

Create a file Resources/config/jobs.yml in our Bundle to configure a new job:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
parameters: ~

services:
    # defines the job
    acme_dummy_connector.job.dummy_job:
        class: '%pim_connector.job.simple_job.class%' # we use existing class for the job
        arguments:
            - 'dummy_job' # this is the job name
            - '@event_dispatcher'
            - '@akeneo_batch.job_repository'
            -
                - '@acme_dummy_connector.step.dummy_step'
        tags:
            - { name: akeneo_batch.job, connector: 'Dummy Connector', type: '%pim_connector.job.export_type%' }

    # defines the step
    acme_dummy_connector.step.dummy_step:
        class: '%pim_connector.step.item_step.class%' # we use existing class for the step
        arguments:
            - 'dummy_step' # this is the step name
            - '@event_dispatcher'
            - '@akeneo_batch.job_repository'
            - '@pim_connector.reader.dummy_item'
            - '@pim_connector.processor.dummy_item'
            - '@pim_connector.writer.dummy_item'

Load this file in the bundle extension:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

namespace Acme\Bundle\DummyConnectorBundle\DependencyInjection;

use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader;

class AcmeDummyConnectorExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('jobs.yml');
    }
}

Here we use an existing dummy reader, a processor and a writer (they implement relevant interfaces and are usable but they do nothing with data).

The reader is implemented in the class Pim\Component\Connector\Reader\DummyItemReader which is defined as a service in the ConnectorBundle with the alias pim_connector.reader.dummy_item in the file Resources\config\readers.yml.

The processor is implemented in the class Pim\Component\Connector\Processor\DummyItemProcessor which is defined as a service in the ConnectorBundle with the alias pim_connector.processor.dummy_item in the file Resources\config\processors.yml.

The writer is implemented in the class Pim\Component\Connector\Writer\DummyItemWriter, which is defined as a service in the ConnectorBundle with the alias pim_connector.writer.dummy_item in the file Resources\config\writers.yml.

We’ll explain in next cookbook chapters how to create your own elements with real logic inside.

Warning

Please note that in versions < 1.6, the file was always named “batch_jobs.yml” and was automatically loaded. The file content was very strict, was less standard and upgradeable than it is now.

Configure our JobParameters

To be executed, a Job is launched with a JobParameters which contains runtime parameters.

In our example, let’s assume that our job needs a file path where to write data. This path must be provided and the directory has to be writable in order to execute the job.

We have to define a couple of services implementing Akeneo\Component\Batch\Job\JobParameters\DefaultValuesProviderInterface and Akeneo\Component\Batch\Job\JobParameters\ConstraintCollectionProviderInterface.

The first service provides the default values used to create a JobParameters instance,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php

namespace Acme\Bundle\DummyConnectorBundle\Job\JobParameters\DefaultValuesProvider;

use Akeneo\Component\Batch\Job\JobInterface;
use Akeneo\Component\Batch\Job\JobParameters\DefaultValuesProviderInterface;

class DummyExport implements DefaultValuesProviderInterface
{
    protected $supportedJobNames;

    public function __construct(array $supportedJobNames)
    {
        $this->supportedJobNames = $supportedJobNames;
    }

    public function getDefaultValues()
    {
        return [
            'filePath'   => '/tmp/dummy.txt',
        ];
    }

    public function supports(JobInterface $job)
    {
        return in_array($job->getName(), $this->supportedJobNames);
    }
}

Tip

If the job doesn’t need any particular parameters, it’s possible to use directly the class Akeneo\Component\Batch\Job\JobParameters\EmptyDefaultValuesProvider.

The second service provides the constraints used to validate each parameter of a JobParameters instance,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace Acme\Bundle\DummyConnectorBundle\Job\JobParameters\ConstraintCollectionProvider;

use Akeneo\Component\Batch\Job\JobInterface;
use Akeneo\Component\Batch\Job\JobParameters\ConstraintCollectionProviderInterface;
use Pim\Bundle\ImportExportBundle\Validator\Constraints\WritableDirectory;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotBlank;

class DummyExport implements ConstraintCollectionProviderInterface
{
    protected $supportedJobNames;

    public function __construct(array $supportedJobNames)
    {
        $this->supportedJobNames = $supportedJobNames;
    }

    public function getConstraintCollection()
    {
        return new Collection(
            [
                'fields' => [
                    'filePath' => [
                        new NotBlank(['groups' => 'Execution']),
                        new WritableDirectory(['groups' => 'Execution'])
                    ],
                ]
            ]
        );
    }

    public function supports(JobInterface $job)
    {
        return in_array($job->getName(), $this->supportedJobNames);
    }
}

Tip

If the job doesn’t need any particular parameters, it’s possible to use directly the class Akeneo\Component\Batch\Job\JobParameters\EmptyConstraintCollectionProvider.

These services use tags and implement supports() method so they can only be used for our job.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
parameters:
    acme_dummy_connector.job.job_parameters.default_values_provider.dummy_export.class:        Acme\Bundle\DummyConnectorBundle\Job\JobParameters\DefaultValuesProvider\DummyExport
    acme_dummy_connector.job.job_parameters.constraint_collection_provider.dummy_export.class: Acme\Bundle\DummyConnectorBundle\Job\JobParameters\ConstraintCollectionProvider\DummyExport

services:
    # Default values for our JobParameters
    acme_dummy_connector.job.job_parameters.default_values_provider.dummy_export:
        class: '%acme_dummy_connector.job.job_parameters.default_values_provider.dummy_export.class%'
        arguments:
            -
                - 'dummy_job' # the job name
        tags:
            - { name: akeneo_batch.job.job_parameters.default_values_provider }

    # Validation constraints for our JobParameters
    acme_dummy_connector.job.job_parameters.constraint_collection_provider.dummy_export:
        class: '%acme_dummy_connector.job.job_parameters.constraint_collection_provider.dummy_export.class%'
        arguments:
            -
                - 'dummy_job' # the job name
        tags:
            - { name: akeneo_batch.job.job_parameters.constraint_collection_provider }

As for the jobs.yml, this service file job_parameters.yml must be loaded in our AcmeDummyConnectorExtension.

Note

We could implement a single class implementing the 2 interfaces and define a single service on both tags.

Create a Job Instance

Each Job can be configured through a JobInstance, an instance of the Job.

It means we can define a job and several instances of it, with different configurations.

Please note that this job instance does not take any configuration.

We can create an instance with the following command:

# akeneo:batch:create-job <connector> <job> <type> <code> <config> [<label>]
php app/console akeneo:batch:create-job 'Dummy Connector' dummy_job export my_job_instance '[]'

You can also list the existing job instances with the following command:

php app/console akeneo:batch:list-jobs

Execute our new Job Instance

You can run the job with the following command:

php app/console akeneo:batch:job my_job_instance

[2016-07-07 16:48:35] batch.DEBUG: Job execution starting: startTime=, endTime=, updatedTime=, status=2, exitStatus=[UNKNOWN] , exitDescription=[], job=[my_job_instance] [] []
[2016-07-07 16:48:35] batch.INFO: Step execution starting: id=0, name=[dummy_step], status=[2], exitCode=[EXECUTING], exitDescription=[] [] []
[2016-07-07 16:48:35] batch.DEBUG: Step execution success: id= 1 [] []
[2016-07-07 16:48:35] batch.DEBUG: Step execution complete: id=1, name=[dummy_step], status=[1], exitCode=[EXECUTING], exitDescription=[] [] []
[2016-07-07 16:48:35] batch.DEBUG: Upgrading JobExecution status: startTime=2016-07-07T14:48:35+00:00, endTime=, updatedTime=, status=3, exitStatus=[UNKNOWN] , exitDescription=[], job=[my_job_instance] [] []
Export my_job_instance has been successfully executed.

The --config option can be used to override the default values parameters, for instance, to change the file path.

php app/console akeneo:batch:job my_job_instance --config='{"filePath":"\/tmp\/new_path.txt"}'

Configure the UI for our JobParameters

At this point, the job is usable in command line though it cannot be configured via the UI.

We need to write a service providing the form type configuration for each parameter of our JobParameters instance.

In our case we want to display a a text field where to fill in the path. The validation constraint defined earlier will also apply here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php

namespace Acme\Bundle\DummyConnectorBundle\Job\JobParameters\FormConfigurationProvider;

use Akeneo\Component\Batch\Job\JobInterface;
use Akeneo\Component\Batch\Model\JobInstance;
use Pim\Bundle\ImportExportBundle\JobParameters\FormConfigurationProviderInterface;

class DummyExport implements FormConfigurationProviderInterface
{
    protected $supportedJobNames;

    public function __construct(array $supportedJobNames)
    {
        $this->supportedJobNames = $supportedJobNames;
    }

    public function getFormConfiguration()
    {
        return [
            'filePath' => [
                'type' => 'text',
                'options' => [
                    'label' => 'pim_connector.export.filePath.label', // label to use in the form
                    'help'  => 'pim_connector.export.filePath.help' // tooltip text to use in the form
                ]
            ],
        ];
    }

    public function supports(JobInterface $job)
    {
        return in_array($job->getName(), $this->supportedJobNames);
    }
}

This service is configured using a tag and it implements supports() method to be used for our job only.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
parameters:
    acme_dummy_connector.job.job_parameters.form_configuration_provider.dummy_export.class:    Acme\Bundle\DummyConnectorBundle\Job\JobParameters\FormConfigurationProvider\DummyExport

services:
    # Form configuration for our JobParameters
    acme_dummy_connector.job.job_parameters.form_configuration_provider.dummy_export:
        class: '%acme_dummy_connector.job.job_parameters.form_configuration_provider.dummy_export.class%'
        arguments:
            -
                - 'dummy_job' # the job name
        tags:
            - { name: pim_import_export.job_parameters.form_configuration_provider }

Translate Job and Step labels in the UI

Behind the scene, the service Pim\Bundle\ImportExportBundle\JobLabel\TranslatedLabelProvider provides translated Job and Step labels to be used in the UI.

This service uses following conventions:
  • for a job label, given a $jobName, “batch_jobs.$jobName.label”
  • for a step label, given a $jobName and a $stepName, “batch_jobs.$jobName.$stepName.label”

Create a file Resources/translations/messages.en.yml in our Bundle to translate label keys.

1
2
batch_jobs.dummy_job.label: Dummy Job
batch_jobs.dummy_job.dummy_step.label: Dummy Step

We can now create a new configuration for our Job from the UI, using the menu “spread > export profiles” then “create export profile” button.

../../_images/create_connector_edit.png