Skip to content
This repository was archived by the owner on Nov 19, 2024. It is now read-only.

Commit 9d653b6

Browse files
authored
Merge pull request #5007 from eduard13/patch-import-entity
Adding a useful topic of how a custom Import Entity can be defined.
2 parents 1783441 + b01f7aa commit 9d653b6

File tree

4 files changed

+375
-0
lines changed

4 files changed

+375
-0
lines changed

_data/toc/extension-best-practices.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,6 @@ pages:
6060

6161
- label: Creating a dynamic row system config
6262
url: /ext-best-practices/tutorials/dynamic-row-system-config.html
63+
64+
- label: Creating a custom import entity
65+
url: /ext-best-practices/tutorials/custom-import-entity.html
Loading
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
---
2+
group: extension-best-practices
3+
title: Custom import entity
4+
contributor_name: Atwix
5+
contributor_link: https://www.atwix.com/
6+
---
7+
8+
This tutorial shows you how to extend the [Magento/ImportExport/Model/Import/Entity/AbstractEntity][0]{:target="_blank"} class to import data into your custom module's table.
9+
The current import entities can be found in **System** > **Import**:
10+
11+
- Advanced Pricing
12+
- Products
13+
- Customers and Addresses (single file)
14+
- Customers Main File
15+
- Customer Addresses
16+
17+
To begin, let's suppose we have a custom table with the following structure:
18+
19+
| entity_id | name | duration |
20+
| --- | --- | --- |
21+
| | | |
22+
23+
## Step 1: Adding a New Entity Type
24+
25+
Declaring our new import entity:
26+
27+
> `etc/import.xml`
28+
29+
```xml
30+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
31+
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_ImportExport:etc/import.xsd">
32+
<entity name="learning" label="Learning Courses Import" model="OrangeCompany\Learning\Model\Import\Courses"
33+
behaviorModel="Magento\ImportExport\Model\Source\Import\Behavior\Basic" />
34+
</config>
35+
```
36+
37+
As we extend the **Magento_ImportExport** module, we should also add a dependency to it in the `module.xml` file.
38+
39+
> `etc/module.xml`
40+
41+
```xml
42+
...
43+
<sequence>
44+
<module name="Magento_ImportExport" />
45+
</sequence>
46+
...
47+
```
48+
49+
## Step 2: Defining the Import Model
50+
51+
As we extend the [Magento/ImportExport/Model/Import/Entity/AbstractEntity][0]{:target="_blank"}, we should implement the following abstract methods:
52+
53+
- `_importData` - Import data rows
54+
- `getEntityTypeCode` - EAV entity type code getter
55+
- `validateRow` - Validating the row
56+
57+
> `OrangeCompany/Learning/Model/Import/Courses.php`
58+
59+
{% collapsible File content for Courses.php %}
60+
61+
```php
62+
namespace OrangeCompany\Learning\Model\Import;
63+
64+
use Exception;
65+
use Magento\Framework\App\ResourceConnection;
66+
use Magento\Framework\DB\Adapter\AdapterInterface;
67+
use Magento\Framework\Json\Helper\Data as JsonHelper;
68+
use Magento\ImportExport\Helper\Data as ImportHelper;
69+
use Magento\ImportExport\Model\Import;
70+
use Magento\ImportExport\Model\Import\Entity\AbstractEntity;
71+
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
72+
use Magento\ImportExport\Model\ResourceModel\Helper;
73+
use Magento\ImportExport\Model\ResourceModel\Import\Data;
74+
75+
/**
76+
* Class Courses
77+
*/
78+
class Courses extends AbstractEntity
79+
{
80+
const ENTITY_CODE = 'learning';
81+
const TABLE = 'learning_courses';
82+
const ENTITY_ID_COLUMN = 'entity_id';
83+
84+
/**
85+
* If we should check column names
86+
*/
87+
protected $needColumnCheck = true;
88+
89+
/**
90+
* Need to log in import history
91+
*/
92+
protected $logInHistory = true;
93+
94+
/**
95+
* Permanent entity columns.
96+
*/
97+
protected $_permanentAttributes = [
98+
'entity_id'
99+
];
100+
101+
/**
102+
* Valid column names
103+
*/
104+
protected $validColumnNames = [
105+
'entity_id',
106+
'name',
107+
'duration'
108+
];
109+
110+
/**
111+
* @var AdapterInterface
112+
*/
113+
protected $connection;
114+
115+
/**
116+
* @var ResourceConnection
117+
*/
118+
private $resource;
119+
120+
/**
121+
* Courses constructor.
122+
*
123+
* @param JsonHelper $jsonHelper
124+
* @param ImportHelper $importExportData
125+
* @param Data $importData
126+
* @param ResourceConnection $resource
127+
* @param Helper $resourceHelper
128+
* @param ProcessingErrorAggregatorInterface $errorAggregator
129+
*/
130+
public function __construct(
131+
JsonHelper $jsonHelper,
132+
ImportHelper $importExportData,
133+
Data $importData,
134+
ResourceConnection $resource,
135+
Helper $resourceHelper,
136+
ProcessingErrorAggregatorInterface $errorAggregator
137+
) {
138+
$this->jsonHelper = $jsonHelper;
139+
$this->_importExportData = $importExportData;
140+
$this->_resourceHelper = $resourceHelper;
141+
$this->_dataSourceModel = $importData;
142+
$this->resource = $resource;
143+
$this->connection = $resource->getConnection(ResourceConnection::DEFAULT_CONNECTION);
144+
$this->errorAggregator = $errorAggregator;
145+
}
146+
147+
/**
148+
* Entity type code getter.
149+
*
150+
* @return string
151+
*/
152+
public function getEntityTypeCode()
153+
{
154+
return static::ENTITY_CODE;
155+
}
156+
157+
/**
158+
* Get available columns
159+
*
160+
* @return array
161+
*/
162+
public function getValidColumnNames(): array
163+
{
164+
return $this->validColumnNames;
165+
}
166+
167+
/**
168+
* Row validation
169+
*
170+
* @param array $rowData
171+
* @param int $rowNum
172+
*
173+
* @return bool
174+
*/
175+
public function validateRow(array $rowData, int $rowNum): bool
176+
{
177+
if (isset($this->_validatedRows[$rowNum])) {
178+
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
179+
}
180+
181+
$this->_validatedRows[$rowNum] = true;
182+
183+
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
184+
}
185+
186+
/**
187+
* Import data
188+
*
189+
* @return bool
190+
*
191+
* @throws Exception
192+
*/
193+
protected function _importData(): bool
194+
{
195+
switch ($this->getBehavior()) {
196+
case Import::BEHAVIOR_DELETE:
197+
$this->deleteEntity();
198+
break;
199+
case Import::BEHAVIOR_REPLACE:
200+
$this->saveAndReplaceEntity();
201+
break;
202+
case Import::BEHAVIOR_APPEND:
203+
$this->saveAndReplaceEntity();
204+
break;
205+
}
206+
207+
return true;
208+
}
209+
210+
/**
211+
* Delete entities
212+
*
213+
* @return bool
214+
*/
215+
private function deleteEntity(): bool
216+
{
217+
$rows = [];
218+
while ($bunch = $this->_dataSourceModel->getNextBunch()) {
219+
foreach ($bunch as $rowNum => $rowData) {
220+
$this->validateRow($rowData, $rowNum);
221+
222+
if (!$this->getErrorAggregator()->isRowInvalid($rowNum)) {
223+
$rowId = $rowData[static::ENTITY_ID_COLUMN];
224+
$rows[] = $rowId;
225+
}
226+
227+
if ($this->getErrorAggregator()->hasToBeTerminated()) {
228+
$this->getErrorAggregator()->addRowToSkip($rowNum);
229+
}
230+
}
231+
}
232+
233+
if ($rows) {
234+
return $this->deleteEntityFinish(array_unique($rows));
235+
}
236+
237+
return false;
238+
}
239+
240+
/**
241+
* Save and replace entities
242+
*
243+
* @return void
244+
*/
245+
private function saveAndReplaceEntity()
246+
{
247+
$behavior = $this->getBehavior();
248+
$rows = [];
249+
while ($bunch = $this->_dataSourceModel->getNextBunch()) {
250+
$entityList = [];
251+
252+
foreach ($bunch as $rowNum => $row) {
253+
if (!$this->validateRow($row, $rowNum)) {
254+
continue;
255+
}
256+
257+
if ($this->getErrorAggregator()->hasToBeTerminated()) {
258+
$this->getErrorAggregator()->addRowToSkip($rowNum);
259+
260+
continue;
261+
}
262+
263+
$rowId = $row[static::ENTITY_ID_COLUMN];
264+
$rows[] = $rowId;
265+
$columnValues = [];
266+
267+
foreach ($this->getAvailableColumns() as $columnKey) {
268+
$columnValues[$columnKey] = $row[$columnKey];
269+
}
270+
271+
$entityList[$rowId][] = $columnValues;
272+
$this->countItemsCreated += (int) !isset($row[static::ENTITY_ID_COLUMN]);
273+
$this->countItemsUpdated += (int) isset($row[static::ENTITY_ID_COLUMN]);
274+
}
275+
276+
if (Import::BEHAVIOR_REPLACE === $behavior) {
277+
if ($rows && $this->deleteEntityFinish(array_unique($rows))) {
278+
$this->saveEntityFinish($entityList);
279+
}
280+
} elseif (Import::BEHAVIOR_APPEND === $behavior) {
281+
$this->saveEntityFinish($entityList);
282+
}
283+
}
284+
}
285+
286+
/**
287+
* Save entities
288+
*
289+
* @param array $entityData
290+
*
291+
* @return bool
292+
*/
293+
private function saveEntityFinish(array $entityData): bool
294+
{
295+
if ($entityData) {
296+
$tableName = $this->connection->getTableName(static::TABLE);
297+
$rows = [];
298+
299+
foreach ($entityData as $entityRows) {
300+
foreach ($entityRows as $row) {
301+
$rows[] = $row;
302+
}
303+
}
304+
305+
if ($rows) {
306+
$this->connection->insertOnDuplicate($tableName, $rows, $this->getAvailableColumns());
307+
308+
return true;
309+
}
310+
311+
return false;
312+
}
313+
}
314+
315+
/**
316+
* Delete entities
317+
*
318+
* @param array $entityIds
319+
*
320+
* @return bool
321+
*/
322+
private function deleteEntityFinish(array $entityIds): bool
323+
{
324+
if ($entityIds) {
325+
try {
326+
$this->countItemsDeleted += $this->connection->delete(
327+
$this->connection->getTableName(static::TABLE),
328+
$this->connection->quoteInto(static::ENTITY_ID_COLUMN . ' IN (?)', $entityIds)
329+
);
330+
331+
return true;
332+
} catch (Exception $e) {
333+
return false;
334+
}
335+
}
336+
337+
return false;
338+
}
339+
340+
/**
341+
* Get available columns
342+
*
343+
* @return array
344+
*/
345+
private function getAvailableColumns(): array
346+
{
347+
return $this->validColumnNames;
348+
}
349+
}
350+
```
351+
352+
{% endcollapsible %}
353+
354+
## Result
355+
356+
As result, we should be able to see the new Entity Type:
357+
358+
![Import Entity]({{ site.baseurl }}/common/images/ext-best-practices/import-entity.png)
359+
360+
Here is a sample csv file we can use to import data into our table.
361+
362+
```text
363+
entity_id,name,duration
364+
,"First Course",90
365+
,"Second Course",120
366+
```
367+
368+
{:.bs-callout .bs-callout-info}
369+
For updating the table's data, you must provide the `entity_id` value for each row.
370+
371+
[0]: {{ site.mage2bloburl }}/{{ page.guide_version }}/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../v2.2/ext-best-practices/tutorials/custom-import-entity.md

0 commit comments

Comments
 (0)