Skip to content
This repository was archived by the owner on Jul 1, 2026. It is now read-only.

Commit eb012ef

Browse files
authored
Merge pull request #8 from akeneo/PIM-8320
PIM-8320: Upload media files during import
2 parents ee3d10a + 051dfa1 commit eb012ef

21 files changed

Lines changed: 230 additions & 98 deletions

config/data_converters.yml

Lines changed: 0 additions & 8 deletions
This file was deleted.

config/services.yaml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,30 @@ services:
1111
autowire: true # Automatically injects dependencies in your services.
1212
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
1313

14+
# Tags
15+
_instanceof:
16+
App\Processor\Converter\DataConverterInterface:
17+
tags: ['app.data_converter']
18+
1419
# makes classes in src/ available to be used as services
1520
# this creates a service per class whose id is the fully-qualified class name
1621
App\:
1722
resource: '../src/*'
1823
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
1924

25+
Box\Spout\Writer\CSV\Writer: ~
26+
2027
Akeneo\PimEnterprise\ApiClient\AkeneoPimEnterpriseClientBuilder:
2128
arguments:
2229
$baseUri: '%env(AKENEO_API_BASE_URI)%'
2330

24-
Box\Spout\Writer\CSV\Writer: ~
31+
Akeneo\PimEnterprise\ApiClient\AkeneoPimEnterpriseClientInterface:
32+
factory: 'Akeneo\PimEnterprise\ApiClient\AkeneoPimEnterpriseClientBuilder:buildAuthenticatedByPassword'
33+
arguments:
34+
- '%env(AKENEO_API_CLIENT_ID)%'
35+
- '%env(AKENEO_API_CLIENT_SECRET)%'
36+
- '%env(AKENEO_API_USERNAME)%'
37+
- '%env(AKENEO_API_PASSWORD)%'
38+
39+
App\Processor\Converter\DataConverter:
40+
arguments: [!tagged app.data_converter]

src/Command/ImportCommand.php

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace App\Command;
66

77
use Akeneo\PimEnterprise\ApiClient\AkeneoPimEnterpriseClientInterface;
8+
use App\ApiClientFactory;
89
use App\FileLogger;
910
use App\Processor\Converter\DataConverter;
1011
use App\Processor\RecordProcessor;
@@ -42,9 +43,6 @@ class ImportCommand extends Command
4243
/** @var StructureGenerator */
4344
private $structureGenerator;
4445

45-
/** @var AkeneoPimEnterpriseClientBuilder */
46-
private $clientBuilder;
47-
4846
/** @var DataConverter */
4947
private $converter;
5048

@@ -68,20 +66,20 @@ class ImportCommand extends Command
6866

6967
public function __construct(
7068
StructureGenerator $structureGenerator,
71-
AkeneoPimEnterpriseClientBuilder $clientBuilder,
7269
DataConverter $converter,
7370
RecordProcessor $processor,
7471
FileLogger $logger,
75-
InvalidFileGenerator $invalidFileGenerator
72+
InvalidFileGenerator $invalidFileGenerator,
73+
AkeneoPimEnterpriseClientInterface $apiClient
7674
) {
7775
parent::__construct(static::$defaultName);
7876

7977
$this->structureGenerator = $structureGenerator;
80-
$this->clientBuilder = $clientBuilder;
8178
$this->converter = $converter;
8279
$this->processor = $processor;
8380
$this->logger = $logger;
8481
$this->invalidFileGenerator = $invalidFileGenerator;
82+
$this->apiClient = $apiClient;
8583
}
8684

8785
protected function configure()
@@ -126,8 +124,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
126124
'If you want to automate this process or don\'t want to use default values, add the --no-interaction flag when you call this command.'
127125
]);
128126

129-
$this->initializeApiClient($input);
130-
131127
$this->io->newLine(2);
132128
$this->io->title(
133129
sprintf(
@@ -168,16 +164,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
168164
}
169165
}
170166

171-
private function initializeApiClient(InputInterface $input): void
172-
{
173-
$this->apiClient = $this->clientBuilder->buildAuthenticatedByPassword(
174-
$input->getOption('apiClientId'),
175-
$input->getOption('apiClientSecret'),
176-
$input->getOption('apiUsername'),
177-
$input->getOption('apiPassword')
178-
);
179-
}
180-
181167
private function fetchReferenceEntityAttributes(string $referenceEntityCode): array
182168
{
183169
$attributes = $this->apiClient->getReferenceEntityAttributeApi()->all($referenceEntityCode);
@@ -259,12 +245,12 @@ private function importRecords(
259245
continue;
260246
}
261247

262-
if (count($this->reader->getHeaders()) !== count($row)) {
263-
$this->logger->skip(sprintf(
248+
if (!$this->isHeaderValid($row)) {
249+
$message = sprintf(
264250
'Skipped line %s: the number of values is not equal to the number of headers',
265251
$lineNumber
266-
));
267-
$this->invalidFileGenerator->fromRow($row, $filePath, $this->reader->getHeaders());
252+
);
253+
$this->skipRowWithMessage($filePath, $row, $message);
268254

269255
continue;
270256
}
@@ -275,7 +261,13 @@ private function importRecords(
275261
$structure = $this->structureGenerator->generate($attributes, $channels);
276262
$validStructure = array_intersect_key($structure, array_flip($validHeaders));
277263

278-
$recordsToWrite[] = $this->processor->process($line, $validStructure);
264+
try {
265+
$recordsToWrite[] = $this->processor->process($line, $validStructure, $filePath);
266+
} catch (\Exception $e) {
267+
$this->skipRowWithMessage($filePath, $row, $e->getMessage());
268+
269+
continue;
270+
}
279271
$linesToWrite[] = $line;
280272

281273
if (count($recordsToWrite) === self::BATCH_SIZE) {
@@ -307,4 +299,15 @@ private function writeRecords(
307299
$this->logger->logResponses($responses);
308300
$this->invalidFileGenerator->fromResponses($responses, $linesToWrite, $filePath, $this->reader->getHeaders());
309301
}
302+
303+
private function isHeaderValid($row): bool
304+
{
305+
return count($this->reader->getHeaders()) === count($row);
306+
}
307+
308+
private function skipRowWithMessage(string $filePath, $row, string $message): void
309+
{
310+
$this->logger->skip($message);
311+
$this->invalidFileGenerator->fromRow($row, $filePath, $this->reader->getHeaders());
312+
}
310313
}

src/InvalidFileGenerator.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
namespace App;
66

7-
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
8-
use Box\Spout\Writer\WriterFactory;
9-
use Box\Spout\Common\Type;
107
use Box\Spout\Writer\WriterInterface;
118
use Box\Spout\Writer\CSV\Writer;
129
use Box\Spout\Common\Helper\GlobalFunctionsHelper;

src/Processor/Converter/DataConverter.php

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,34 @@
44

55
namespace App\Processor\Converter;
66

7-
use App\YamlReader;
8-
97
/**
108
* Prepare the data to be sent to the API, using registered dedicated converters
119
*
1210
* @author Adrien Pétremann <adrien.petremann@akeneo.com>
1311
* @copyright 2019 Akeneo SAS (https://www.akeneo.com)
1412
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
1513
*/
16-
class DataConverter implements DataConverterInterface
14+
class DataConverter
1715
{
1816
/** @var DataConverterInterface[] */
19-
private $converters;
20-
21-
/** @var YamlReader */
22-
private $yamlReader;
17+
private $converters = [];
2318

24-
public function __construct(YamlReader $yamlReader)
19+
public function __construct(iterable $dataConverters)
2520
{
26-
$this->yamlReader = $yamlReader;
27-
$dataConvertersConfig = $this->yamlReader->parseFile('config/data_converters.yml');
28-
$dataConverterClasses = $dataConvertersConfig['data_converters'];
29-
30-
foreach ($dataConverterClasses as $dataConverterClass) {
31-
$this->converters[] = new $dataConverterClass();
32-
}
21+
$array = [];
22+
array_push($array, ...$dataConverters);
23+
$this->converters = $array;
3324
}
3425

3526
/**
3627
* {@inheritdoc}
3728
*/
38-
public function convert(array $attribute, string $data)
29+
public function convert(array $attribute, string $data, array $context)
3930
{
4031
/** @var DataConverterInterface $converter */
4132
foreach ($this->converters as $converter) {
4233
if ($converter->support($attribute)) {
43-
return $converter->convert($attribute, $data);
34+
return $converter->convert($attribute, $data, $context);
4435
}
4536
}
4637

src/Processor/Converter/DataConverterInterface.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ public function support(array $attribute): bool;
2121
/**
2222
* Convert the given $data for the given $attribute to the correct format expected by the Akeneo API
2323
*/
24-
public function convert(array $attribute, string $data);
24+
public function convert(array $attribute, string $data, array $context);
2525
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Processor\Converter;
6+
7+
use Akeneo\PimEnterprise\ApiClient\AkeneoPimEnterpriseClientInterface;
8+
use Symfony\Component\Filesystem\Filesystem;
9+
10+
/**
11+
* @author Samir Boulil <samir.boulil@akeneo.com>
12+
* @copyright 2019 Akeneo SAS (http://www.akeneo.com)
13+
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
14+
*/
15+
class MediaAttributeConverter implements DataConverterInterface
16+
{
17+
private const IMAGE_ATTRIBUTE_TYPE = 'image';
18+
19+
/** @var AkeneoPimEnterpriseClientInterface */
20+
private $pimClient;
21+
22+
/** @var Filesystem */
23+
private $filesystem;
24+
25+
public function __construct(AkeneoPimEnterpriseClientInterface $pimClient, Filesystem $filesystem)
26+
{
27+
$this->filesystem = $filesystem;
28+
$this->pimClient = $pimClient;
29+
}
30+
31+
public function support(array $attribute): bool
32+
{
33+
return self::IMAGE_ATTRIBUTE_TYPE === $attribute['type'];
34+
}
35+
36+
public function convert(array $attribute, string $data, array $context)
37+
{
38+
$mediaFilePath = $this->mediaFilePath($data, $context);
39+
$this->checkMediaExists($mediaFilePath);
40+
$mediaIdentifier = $this->uploadMediaToPIM($mediaFilePath);
41+
42+
return $mediaIdentifier;
43+
}
44+
45+
private function mediaFilePath(string $relativeMediaPath, array $context): string
46+
{
47+
$fileToImportPath = $context['filePath'];
48+
49+
return sprintf('%s%s%s', dirname($fileToImportPath), DIRECTORY_SEPARATOR, $relativeMediaPath);
50+
}
51+
52+
private function checkMediaExists(string $mediaFilePath): void
53+
{
54+
if (!$this->filesystem->exists($mediaFilePath)) {
55+
throw new \RuntimeException(sprintf('media file at path "%s" was not found.', $mediaFilePath));
56+
}
57+
}
58+
59+
private function uploadMediaToPIM(string $mediaFilePath): string
60+
{
61+
try {
62+
$mediaIdentifier = $this->pimClient->getReferenceEntityMediaFileApi()->create($mediaFilePath);
63+
64+
return $mediaIdentifier;
65+
} catch (\Exception $exception) {
66+
$message = sprintf(
67+
'An error occured while uploading the media at path "%s" to the PIM: %s',
68+
$mediaFilePath,
69+
$exception->getMessage()
70+
);
71+
72+
throw new \RuntimeException($message);
73+
}
74+
}
75+
}

src/Processor/Converter/MultipleOptionsConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function support(array $attribute): bool
2424
/**
2525
* {@inheritdoc}
2626
*/
27-
public function convert(array $attribute, string $data)
27+
public function convert(array $attribute, string $data, array $context)
2828
{
2929
if (empty($data)) {
3030
return [];

src/Processor/Converter/ReferenceEntityMultipleLinksConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function support(array $attribute): bool
2424
/**
2525
* {@inheritdoc}
2626
*/
27-
public function convert(array $attribute, string $data)
27+
public function convert(array $attribute, string $data, array $context)
2828
{
2929
if (empty($data)) {
3030
return [];

src/Processor/Converter/ReferenceEntitySingleLinkConverter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function support(array $attribute): bool
2424
/**
2525
* {@inheritdoc}
2626
*/
27-
public function convert(array $attribute, string $data)
27+
public function convert(array $attribute, string $data, array $context)
2828
{
2929
return $data;
3030
}

0 commit comments

Comments
 (0)