Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exporter infrastructure #37

Merged
merged 12 commits into from
Mar 1, 2018
Merged
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/bin/*
/bin/
!/bin/.gitkeep

/vendor/
Expand All @@ -9,3 +9,4 @@
!/etc/build/.gitkeep

/tests/Application/yarn.lock
/tests/Behat/Resources/fixtures/countries_export.csv
107 changes: 107 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ sylius_import_export:
* payment_method (csv, excel)
* tax_category (csv, excel)

### Available exporter types

* country (csv)

## Example import files

See the fixtures in the Behat tests: `tests/Behat/Resources/fixtures`
Expand All @@ -97,6 +101,11 @@ admin overview panel using the event hook system, ie. `admin/tax-categories/`.
```bash
$ bin/console sylius:import tax_category my/tax/categories/csv/file.csv --format=csv
```

- Export data of resources to file using `country` exporter
```bash
$ bin/console sylius:export country my/countries/export/csv/file.csv --format=csv
```

## Development

Expand Down Expand Up @@ -187,6 +196,104 @@ Each Processor has defined mandatory 'HeaderKeys'. For basic validation of these
"@sylius.importer.metadata_validator". Of course it is also possible to implement you own Validator, by implementing the
MetadataValidatorInterface and injecting it in your FooProcessor instead of the generic one.

### Defining new Exporters
#### Notes

- Replace `foo` with the name of the type you want to implement in the following examples.
- Replace `bar` with the name of the format you want to implement in the following examples.
- Note it is of course also possible to implement a dedicated exporter for `foo` type and format `bar`,
in case a generic type implementation is not possible.

### Exporters
#### Adding a ResourceExporter

Define your ResourceExporter in services_bar.yml (at the moment only csv is supported for export)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While reading this without any knowledge about the internal workings, I found it rather hard to understand what I have to do to get it working quickly. I think it would be good to extend the documentation with some more 'real' examples (no foo, bar) but also more explanation about the concept of type, format and plugins.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a example based on the existing exporter. Also added a section where I described my idea of the PluginPool.


```yaml
sylius.exporter.foo.bar:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\ResourceExporter
arguments:
- "@sylius.exporter.bar_writer"
- "@sylius.exporter.pluginpool.foo"
- ["HeaderKey0", "HeaderKey1" ,"HeaderKey2"]
tags:
- { name: sylius.exporter, type: foo, format: bar }
```

Define the PluginPool for your ResourceExporter in services.yml

```yaml
# PluginPools for Exporters. Can contain multiple Plugins
sylius.exporter.pluginpool.foo:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginPool
arguments:
- ["@sylius.exporter.plugin.resource.foo"]
- ["HeaderKey0", "HeaderKey1" ,"HeaderKey2"]
```

Define the Plugin for your FooResource in services.yml

```yaml
# Plugins for Exporters
sylius.exporter.plugin.resource.foo:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\ResourcePlugin
arguments:
- "@sylius.repository.foo"
- "@property_accessor"
- "@doctrine.orm.entity_manager"

```

### A real example
Define the Countries-Exporter in services_csv.yml
```yaml
sylius.exporter.countries.csv:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\ResourceExporter
arguments:
- "@sylius.exporter.csv_writer"
- "@sylius.exporter.pluginpool.countries"
- ["Id", "Code" ,"Enabled"]
tags:
- { name: sylius.exporter, type: country, format: csv }
```

Define the PluginPool for the Countries-Exporter in services.yml

```yaml
# PluginPools for Exporters. Can contain multiple Plugins
sylius.exporter.pluginpool.countries:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginPool
arguments:
- ["@sylius.exporter.plugin.resource.country"]
- ["Id", "Code" ,"Enabled"]
```

Define the Plugin for the Country-Resource in services.yml

```yaml
# Plugins for Exporters
sylius.exporter.plugin.resource.countries:
class: FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\ResourcePlugin
arguments:
- "@sylius.repository.country"
- "@property_accessor"
- "@doctrine.orm.entity_manager"

```

The exporter will instantly be available as a exporter for the command line.

```bash
$ bin/console sylius:export country my/countries/export/csv/file.csv --format=csv
```

### PluginPool
The idea behind the plugin pool is, to be able to have different kind of plugins, which could possibly
provide data based on a custom sql that queries additional data for the exported resource, such as the
preferred brand of a customer.
At the moment there are only 'ResourcePlugin's, which allow the complete export of all data of one resource at the moment.
With the provided keys you can influence which fields of a resource are exported.

### Running plugin tests

- Test application install
Expand Down
13 changes: 13 additions & 0 deletions features/export/cli/exporting_countries_to_csv_via_cli.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@managing_countries
Feature: exporting countries to csv-file
In order to have my countries exported to an external target
As a Developer
I want to be able to export country data to csv file from the commandline

Background:
Given I have a working command-line-interface

@cli_importer_exporter
Scenario: Exporting countries to csv-file
When I export "country" data as "csv" to the file "countries_export.csv" with the cli-command
Then I should see "Exported" in the output
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Feature: Importing countries from csv with the command-line-interface
Background:
Given I have a working command-line-interface

@cli_importer
@cli_importer_exporter
Scenario: Importing defined countries with the cli-command
When I import "country" data from csv file "countries.csv" file with the cli-command
Then I should see "Imported" in the output
Expand Down
61 changes: 61 additions & 0 deletions spec/DependencyInjection/Compiler/RegisterExporterPassSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace spec\FriendsOfSylius\SyliusImportExportPlugin\DependencyInjection\Compiler;

use FriendsOfSylius\SyliusImportExportPlugin\DependencyInjection\Compiler\RegisterExporterPass;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class RegisterExporterPassSpec extends ObjectBehavior
{
function it_is_a_compiler_pass(): void
{
$this->shouldImplement(CompilerPassInterface::class);
}

function it_is_initializable()
{
$this->shouldHaveType(RegisterExporterPass::class);
}

function it_processes_the_exporter_services(
ContainerBuilder $container,
Definition $exporterRegistry,
Definition $blockEventDefinition
) {
$exporterType = 'csv';
/**
* prepare the mock for the container builder
*/
$container->getParameter('sylius.importer.web_ui')->willReturn(true);
$container->has('sylius.exporters_registry')->willReturn(true);
$container->has(Argument::type('string'))->willReturn(false);
$container->findDefinition('sylius.exporters_registry')->willReturn($exporterRegistry);
$container->findTaggedServiceIds('sylius.exporter')->willReturn([
'exporter_id' => [
[
'type' => $exporterType,
'format' => 'exporter_format',
],
],
]);

/**
* prepare the mock for the exporterRegistry
*/
$exporterRegistry->addMethodCall(
'register',
Argument::type('array')
)->shouldBeCalled();

/**
* run the test
*/
$this->process($container);
}
}
22 changes: 22 additions & 0 deletions spec/Exporter/Plugin/PluginFactorySpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace spec\FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin;

use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginFactory;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginFactoryInterface;
use PhpSpec\ObjectBehavior;

class PluginFactorySpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(PluginFactory::class);
}

function it_implements_the_plugin_factory_interface()
{
$this->shouldImplement(PluginFactoryInterface::class);
}
}
88 changes: 88 additions & 0 deletions spec/Exporter/Plugin/PluginPoolSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);

namespace spec\FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin;

use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginInterface;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginPool;
use FriendsOfSylius\SyliusImportExportPlugin\Exporter\Plugin\PluginPoolInterface;
use PhpSpec\ObjectBehavior;

class PluginPoolSpec extends ObjectBehavior
{
function let(
PluginInterface $plugin1,
PluginInterface $plugin2
) {
$this->beConstructedWith([$plugin1, $plugin2], ['description', 'name']);
}

function it_is_initializable()
{
$this->shouldHaveType(PluginPool::class);
}

function it_implements_the_plugin_pool_interface()
{
$this->shouldImplement(PluginPoolInterface::class);
}

function it_returns_array_of_plugins_after_creation(
PluginInterface $plugin1,
PluginInterface $plugin2
) {
$this
->getPlugins()
->shouldReturn(
[
$plugin1,
$plugin2,
]
);
}

function it_inits_plugins_with_ids(
PluginInterface $plugin1,
PluginInterface $plugin2
) {
$ids = [
'id1',
'id2',
'id3',
];
$plugin1->init($ids)->shouldBeCalled();
$plugin2->init($ids)->shouldBeCalled();
$this->initPlugins($ids);
}

function it_gets_correct_data_from_multiple_plugins(
PluginInterface $plugin1,
PluginInterface $plugin2
) {
$plugin1
->getData('id1', ['description', 'name'])
->willReturn(
[
'description' => '',
'name' => 'testName',
]
);
$plugin2
->getData('id1', ['description', 'name'])
->willReturn(
[
'description' => 'this is a description',
'name' => '',
]
);

$this->getDataForId('id1')
->shouldReturn(
[
'description' => 'this is a description',
'name' => 'testName',
]
);
}
}
Loading