Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
AbstractEntity.php
Go to the documentation of this file.
1 <?php
7 
14 
24 abstract class AbstractEntity
25 {
29  const COLUMN_ACTION = '_action';
30 
34  const COLUMN_ACTION_VALUE_DELETE = 'delete';
35 
39  const XML_PATH_BUNCH_SIZE = 'import/format_v2/bunch_size';
40 
41  const XML_PATH_PAGE_SIZE = 'import/format_v2/page_size';
42 
48  const DB_MAX_VARCHAR_LENGTH = 256;
49 
50  const DB_MAX_TEXT_LENGTH = 65536;
51 
52  const ERROR_CODE_SYSTEM_EXCEPTION = 'systemException';
53  const ERROR_CODE_COLUMN_NOT_FOUND = 'columnNotFound';
54  const ERROR_CODE_COLUMN_EMPTY_HEADER = 'columnEmptyHeader';
55  const ERROR_CODE_COLUMN_NAME_INVALID = 'columnNameInvalid';
56  const ERROR_CODE_ATTRIBUTE_NOT_VALID = 'attributeNotInvalid';
57  const ERROR_CODE_DUPLICATE_UNIQUE_ATTRIBUTE = 'duplicateUniqueAttribute';
58  const ERROR_CODE_ILLEGAL_CHARACTERS = 'illegalCharacters';
59  const ERROR_CODE_INVALID_ATTRIBUTE = 'invalidAttributeName';
60  const ERROR_CODE_WRONG_QUOTES = 'wrongQuotes';
61  const ERROR_CODE_COLUMNS_NUMBER = 'wrongColumnsNumber';
62  const ERROR_EXCEEDED_MAX_LENGTH = 'exceededMaxLength';
63  const ERROR_INVALID_ATTRIBUTE_TYPE = 'invalidAttributeType';
64  const ERROR_INVALID_ATTRIBUTE_OPTION = 'absentAttributeOption';
65 
69  protected $errorMessageTemplates = [
70  self::ERROR_CODE_SYSTEM_EXCEPTION => 'General system exception happened',
71  self::ERROR_CODE_COLUMN_NOT_FOUND => 'We can\'t find required columns: %s.',
72  self::ERROR_CODE_COLUMN_EMPTY_HEADER => 'Columns number: "%s" have empty headers',
73  self::ERROR_CODE_COLUMN_NAME_INVALID => 'Column names: "%s" are invalid',
74  self::ERROR_CODE_ATTRIBUTE_NOT_VALID => "Please correct the value for '%s'",
75  self::ERROR_CODE_DUPLICATE_UNIQUE_ATTRIBUTE => "Duplicate Unique Attribute for '%s'",
76  self::ERROR_CODE_ILLEGAL_CHARACTERS => "Illegal character used for attribute %s",
77  self::ERROR_CODE_INVALID_ATTRIBUTE => 'Header contains invalid attribute(s): "%s"',
78  self::ERROR_CODE_WRONG_QUOTES => "Curly quotes used instead of straight quotes",
79  self::ERROR_CODE_COLUMNS_NUMBER => "Number of columns does not correspond to the number of rows in the header",
80  self::ERROR_EXCEEDED_MAX_LENGTH => 'Attribute %s exceeded max length',
81  self::ERROR_INVALID_ATTRIBUTE_TYPE => 'Value for \'%s\' attribute contains incorrect value',
82  self::ERROR_INVALID_ATTRIBUTE_OPTION => "Value for %s attribute contains incorrect value"
83  . ", see acceptable values on settings specified for Admin",
84  ];
85 
89  protected $_connection;
90 
96  protected $_dataValidated = false;
97 
103  protected $validColumnNames = [];
104 
110  protected $needColumnCheck = false;
111 
117  protected $_dataSourceModel;
118 
122  protected $errorAggregator;
123 
129  protected $_importAllowed = true;
130 
136  protected $string;
137 
143  protected $_parameters = [];
144 
151 
157  protected $_permanentAttributes = [];
158 
165 
171  protected $_processedRowsCount = 0;
172 
178  protected $logInHistory = true;
179 
189  protected $_skippedRows = [];
190 
196  protected $_validatedRows = [];
197 
203  protected $_source;
204 
210  protected $_uniqueAttributes = [];
211 
217  protected $_availableBehaviors = [
221  ];
222 
228  protected $_pageSize;
229 
235  protected $_maxDataSize;
236 
242  protected $_bunchSize;
243 
250 
256  protected $_scopeConfig;
257 
263  protected $countItemsCreated = 0;
264 
270  protected $countItemsUpdated = 0;
271 
277  protected $countItemsDeleted = 0;
278 
284  private $serializer;
285 
296  public function __construct(
297  \Magento\Framework\Stdlib\StringUtils $string,
298  \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
299  \Magento\ImportExport\Model\ImportFactory $importFactory,
300  \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper,
303  array $data = []
304  ) {
305  $this->_scopeConfig = $scopeConfig;
306  $this->_dataSourceModel = isset(
307  $data['data_source_model']
308  ) ? $data['data_source_model'] : $importFactory->create()->getDataSourceModel();
309  $this->_connection =
310  isset($data['connection']) ?
311  $data['connection'] :
312  $resource->getConnection();
313  $this->string = $string;
314  $this->_pageSize = isset(
315  $data['page_size']
316  ) ? $data['page_size'] : (static::XML_PATH_PAGE_SIZE ? (int)$this->_scopeConfig->getValue(
317  static::XML_PATH_PAGE_SIZE,
318  \Magento\Store\Model\ScopeInterface::SCOPE_STORE
319  ) : 0);
320  $this->_maxDataSize = isset(
321  $data['max_data_size']
322  ) ? $data['max_data_size'] : $resourceHelper->getMaxDataSize();
323  $this->_bunchSize = isset(
324  $data['bunch_size']
325  ) ? $data['bunch_size'] : (static::XML_PATH_BUNCH_SIZE ? (int)$this->_scopeConfig->getValue(
326  static::XML_PATH_BUNCH_SIZE,
327  \Magento\Store\Model\ScopeInterface::SCOPE_STORE
328  ) : 0);
329 
330  $this->errorAggregator = $errorAggregator;
331 
332  foreach ($this->errorMessageTemplates as $errorCode => $message) {
333  $this->getErrorAggregator()->addErrorMessageTemplate($errorCode, $message);
334  }
335  }
336 
340  public function getErrorAggregator()
341  {
342  return $this->errorAggregator;
343  }
344 
351  abstract protected function _importData();
352 
359  abstract public function getEntityTypeCode();
360 
367  protected function _prepareRowForDb(array $rowData)
368  {
374  foreach ($rowData as $key => $val) {
375  if ($val === '') {
376  $rowData[$key] = null;
377  }
378  }
379  return $rowData;
380  }
381 
389  protected function addErrors($code, $errors)
390  {
391  if ($errors) {
392  $this->getErrorAggregator()->addError(
393  $code,
395  null,
396  implode('", "', $errors)
397  );
398  }
399  }
400 
408  protected function _saveValidatedBunches()
409  {
410  $source = $this->getSource();
411  $bunchRows = [];
412  $startNewBunch = false;
413 
414  $source->rewind();
415  $this->_dataSourceModel->cleanBunches();
417 
418  while ($source->valid() || count($bunchRows) || isset($entityGroup)) {
419  if ($startNewBunch || !$source->valid()) {
420  /* If the end approached add last validated entity group to the bunch */
421  if (!$source->valid() && isset($entityGroup)) {
422  foreach ($entityGroup as $key => $value) {
423  $bunchRows[$key] = $value;
424  }
425  unset($entityGroup);
426  }
427  $this->_dataSourceModel->saveBunch($this->getEntityTypeCode(), $this->getBehavior(), $bunchRows);
428 
429  $bunchRows = [];
430  $startNewBunch = false;
431  }
432  if ($source->valid()) {
433  $valid = true;
434  try {
435  $rowData = $source->current();
436  foreach ($rowData as $attrName => $element) {
437  if (!mb_check_encoding($element, 'UTF-8')) {
438  $valid = false;
439  $this->addRowError(
441  $this->_processedRowsCount,
442  $attrName
443  );
444  }
445  }
446  } catch (\InvalidArgumentException $e) {
447  $valid = false;
448  $this->addRowError($e->getMessage(), $this->_processedRowsCount);
449  }
450  if (!$valid) {
451  $this->_processedRowsCount++;
452  $source->next();
453  continue;
454  }
455 
456  if (isset($rowData[$masterAttributeCode]) && trim($rowData[$masterAttributeCode])) {
457  /* Add entity group that passed validation to bunch */
458  if (isset($entityGroup)) {
459  foreach ($entityGroup as $key => $value) {
460  $bunchRows[$key] = $value;
461  }
462  $productDataSize = strlen($this->getSerializer()->serialize($bunchRows));
463 
464  /* Check if the new bunch should be started */
465  $isBunchSizeExceeded = ($this->_bunchSize > 0 && count($bunchRows) >= $this->_bunchSize);
466  $startNewBunch = $productDataSize >= $this->_maxDataSize || $isBunchSizeExceeded;
467  }
468 
469  /* And start a new one */
470  $entityGroup = [];
471  }
472 
473  if (isset($entityGroup) && $this->validateRow($rowData, $source->key())) {
474  /* Add row to entity group */
475  $entityGroup[$source->key()] = $this->_prepareRowForDb($rowData);
476  } elseif (isset($entityGroup)) {
477  /* In case validation of one line of the group fails kill the entire group */
478  unset($entityGroup);
479  }
480 
481  $this->_processedRowsCount++;
482  $source->next();
483  }
484  }
485  return $this;
486  }
487 
496  private function getSerializer()
497  {
498  if (null === $this->serializer) {
499  $this->serializer = ObjectManager::getInstance()->get(Json::class);
500  }
501  return $this->serializer;
502  }
503 
515  public function addRowError(
516  $errorCode,
517  $errorRowNum,
518  $colName = null,
519  $errorMessage = null,
521  $errorDescription = null
522  ) {
523  $errorCode = (string)$errorCode;
524  $this->getErrorAggregator()->addError(
525  $errorCode,
526  $errorLevel,
527  $errorRowNum,
528  $colName,
529  $errorMessage,
530  $errorDescription
531  );
532 
533  return $this;
534  }
535 
543  public function addMessageTemplate($errorCode, $message)
544  {
545  $this->getErrorAggregator()->addErrorMessageTemplate($errorCode, $message);
546 
547  return $this;
548  }
549 
556  public function getBehavior(array $rowData = null)
557  {
558  if (isset(
559  $this->_parameters['behavior']
560  ) && in_array(
561  $this->_parameters['behavior'],
562  $this->_availableBehaviors
563  )
564  ) {
565  $behavior = $this->_parameters['behavior'];
566  if ($rowData !== null && $behavior == \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM) {
567  // try analyze value in self::COLUMN_CUSTOM column and return behavior for given $rowData
568  if (array_key_exists(self::COLUMN_ACTION, $rowData)) {
569  if (strtolower($rowData[self::COLUMN_ACTION]) == self::COLUMN_ACTION_VALUE_DELETE) {
571  } else {
572  // as per task description, if column value is different to self::COLUMN_CUSTOM_VALUE_DELETE,
573  // we should always use default behavior
574  return self::getDefaultBehavior();
575  }
576  if (in_array($behavior, $this->_availableBehaviors)) {
577  return $behavior;
578  }
579  }
580  } else {
581  // if method is invoked without $rowData we should just return $this->_parameters['behavior']
582  return $behavior;
583  }
584  }
585 
586  return self::getDefaultBehavior();
587  }
588 
594  public static function getDefaultBehavior()
595  {
596  return \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE;
597  }
598 
604  public function getProcessedEntitiesCount()
605  {
607  }
608 
614  public function getProcessedRowsCount()
615  {
617  }
618 
625  public function getSource()
626  {
627  if (!$this->_source) {
628  throw new \Magento\Framework\Exception\LocalizedException(__('The source is not set.'));
629  }
630  return $this->_source;
631  }
632 
638  public function importData()
639  {
640  return $this->_importData();
641  }
642 
650  {
651  return in_array($attributeCode, $this->_specialAttributes);
652  }
653 
657  public function getMasterAttributeCode()
658  {
660  }
661 
673  public function isAttributeValid(
675  array $attributeParams,
676  array $rowData,
677  $rowNumber,
679  ) {
680  $message = '';
681  switch ($attributeParams['type']) {
682  case 'varchar':
683  $value = $this->string->cleanString($rowData[$attributeCode]);
684  $valid = $this->string->strlen($value) < self::DB_MAX_VARCHAR_LENGTH;
686  break;
687  case 'decimal':
688  $value = trim($rowData[$attributeCode]);
689  $valid = (double)$value == $value && is_numeric($value);
691  break;
692  case 'select':
693  case 'multiselect':
694  case 'boolean':
695  $valid = true;
696  foreach (explode($multiSeparator, mb_strtolower($rowData[$attributeCode])) as $value) {
697  $valid = isset($attributeParams['options'][$value]);
698  if (!$valid) {
699  break;
700  }
701  }
703  break;
704  case 'int':
705  $value = trim($rowData[$attributeCode]);
706  $valid = (int)$value == $value && is_numeric($value);
708  break;
709  case 'datetime':
710  $value = trim($rowData[$attributeCode]);
711  $valid = strtotime($value) !== false;
713  break;
714  case 'text':
715  $value = $this->string->cleanString($rowData[$attributeCode]);
716  $valid = $this->string->strlen($value) < self::DB_MAX_TEXT_LENGTH;
718  break;
719  default:
720  $valid = true;
721  break;
722  }
723 
724  if (!$valid) {
725  if ($message == self::ERROR_INVALID_ATTRIBUTE_TYPE) {
726  $message = sprintf(
727  $this->errorMessageTemplates[$message],
729  $attributeParams['type']
730  );
731  }
732  $this->addRowError($message, $rowNumber, $attributeCode);
733  } elseif (!empty($attributeParams['is_unique'])) {
734  if (isset($this->_uniqueAttributes[$attributeCode][$rowData[$attributeCode]])) {
735  $this->addRowError(self::ERROR_CODE_DUPLICATE_UNIQUE_ATTRIBUTE, $rowNumber, $attributeCode);
736  return false;
737  }
738  $this->_uniqueAttributes[$attributeCode][$rowData[$attributeCode]] = true;
739  }
740  return (bool)$valid;
741  }
742 
748  public function isImportAllowed()
749  {
750  return $this->_importAllowed;
751  }
752 
760  public function isRowAllowedToImport(array $rowData, $rowNumber)
761  {
762  return $this->validateRow($rowData, $rowNumber) && !isset($this->_skippedRows[$rowNumber]);
763  }
764 
770  public function isNeedToLogInHistory()
771  {
772  return $this->logInHistory;
773  }
774 
782  abstract public function validateRow(array $rowData, $rowNumber);
783 
790  public function setParameters(array $parameters)
791  {
792  $this->_parameters = $parameters;
793  return $this;
794  }
795 
803  {
804  $this->_source = $source;
805  $this->_dataValidated = false;
806 
807  return $this;
808  }
809 
816  public function validateData()
817  {
818  if (!$this->_dataValidated) {
819  $this->getErrorAggregator()->clear();
820  // do all permanent columns exist?
821  $absentColumns = array_diff($this->_permanentAttributes, $this->getSource()->getColNames());
822  $this->addErrors(self::ERROR_CODE_COLUMN_NOT_FOUND, $absentColumns);
823 
824  // check attribute columns names validity
825  $columnNumber = 0;
826  $emptyHeaderColumns = [];
827  $invalidColumns = [];
828  $invalidAttributes = [];
829  foreach ($this->getSource()->getColNames() as $columnName) {
830  $columnNumber++;
831  if (!$this->isAttributeParticular($columnName)) {
832  if (trim($columnName) == '') {
833  $emptyHeaderColumns[] = $columnNumber;
834  } elseif (!preg_match('/^[a-z][a-z0-9_]*$/', $columnName)) {
835  $invalidColumns[] = $columnName;
836  } elseif ($this->needColumnCheck && !in_array($columnName, $this->getValidColumnNames())) {
837  $invalidAttributes[] = $columnName;
838  }
839  }
840  }
841  $this->addErrors(self::ERROR_CODE_INVALID_ATTRIBUTE, $invalidAttributes);
842  $this->addErrors(self::ERROR_CODE_COLUMN_EMPTY_HEADER, $emptyHeaderColumns);
843  $this->addErrors(self::ERROR_CODE_COLUMN_NAME_INVALID, $invalidColumns);
844 
845  if (!$this->getErrorAggregator()->getErrorsCount()) {
846  $this->_saveValidatedBunches();
847  $this->_dataValidated = true;
848  }
849  }
850  return $this->getErrorAggregator();
851  }
852 
858  public function getCreatedItemsCount()
859  {
861  }
862 
868  public function getUpdatedItemsCount()
869  {
871  }
872 
878  public function getDeletedItemsCount()
879  {
881  }
882 
891  protected function updateItemsCounterStats(array $created = [], array $updated = [], array $deleted = [])
892  {
893  $this->countItemsCreated = count($created);
894  $this->countItemsUpdated = count($updated);
895  $this->countItemsDeleted = count($deleted);
896  return $this;
897  }
898 
904  public function getValidColumnNames()
905  {
907  }
908 }
isAttributeValid( $attributeCode, array $attributeParams, array $rowData, $rowNumber, $multiSeparator=Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR)
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$source
Definition: source.php:23
addRowError( $errorCode, $errorRowNum, $colName=null, $errorMessage=null, $errorLevel=ProcessingError::ERROR_LEVEL_CRITICAL, $errorDescription=null)
__()
Definition: __.php:13
$resource
Definition: bulk.php:12
$message
__construct(\Magento\Framework\Stdlib\StringUtils $string, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\ImportExport\Model\ImportFactory $importFactory, \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper, ResourceConnection $resource, ProcessingErrorAggregatorInterface $errorAggregator, array $data=[])
$value
Definition: gender.phtml:16
serialize($keys=[], $valueSeparator='=', $fieldSeparator=' ', $quote='"')
Definition: DataObject.php:446
$attributeCode
Definition: extend.phtml:12
updateItemsCounterStats(array $created=[], array $updated=[], array $deleted=[])
$errors
Definition: overview.phtml:9
$code
Definition: info.phtml:12
$element
Definition: element.phtml:12