![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-eav/Model/Entity/ |
<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Eav\Model\Entity; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend; use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface; use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface as DefaultAttributesProvider; use Magento\Framework\App\Config\Element; use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Model\ResourceModel\AbstractResource; use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor; use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface; /** * Entity/Attribute/Model - entity abstract * * phpcs:disable Magento2.Classes.AbstractApi * @api * @author Magento Core Team <[email protected]> * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ abstract class AbstractEntity extends AbstractResource implements EntityInterface, DefaultAttributesProvider { /** * @var \Magento\Eav\Model\Entity\AttributeLoaderInterface * @since 100.1.0 */ protected $attributeLoader; /** * The connection name. * * @var string */ protected $connectionName; /** * Entity type configuration * * @var Type */ protected $_type; /** * Attributes array by attribute name * * @var array */ protected $_attributesByCode = []; /** * Attributes stored by scope (store id and attribute set id). * * @var array */ private $attributesByScope = []; /** * Two-dimensional array by table name and attribute name * * @var array */ protected $_attributesByTable = []; /** * Attributes that are static fields in entity table * * @var array */ protected $_staticAttributes = []; /** * @var string */ protected $_entityTable; /** * Describe data for tables * * @var array */ protected $_describeTable = []; /** * Entity table identification field name * * @var string */ protected $_entityIdField; /** * Entity primary key for link field name * * @var string * @since 100.1.0 */ protected $linkIdField; /** * Entity values table identification field name * * @var string */ protected $_valueEntityIdField; /** * Entity value table prefix * * @var string */ protected $_valueTablePrefix; /** * Entity table string * * @var string */ protected $_entityTablePrefix; /** * Partial load flag * * @var bool */ protected $_isPartialLoad = false; /** * Partial save flag * * @var bool */ protected $_isPartialSave = false; /** * Attribute set id which used for get sorted attributes * * @var int */ protected $_sortingSetId = null; /** * Entity attribute values per backend table to delete * * @var array */ protected $_attributeValuesToDelete = []; /** * Entity attribute values per backend table to save * * @var array */ protected $_attributeValuesToSave = []; /** * Array of describe attribute backend tables * The table name as key * * @var array */ protected static $_attributeBackendTables = []; /** * @var \Magento\Framework\App\ResourceConnection */ protected $_resource; /** * @var \Magento\Eav\Model\Config */ protected $_eavConfig; /** * @var \Magento\Eav\Model\Entity\Attribute\Set */ protected $_attrSetEntity; /** * @var \Magento\Framework\Locale\FormatInterface */ protected $_localeFormat; /** * @var \Magento\Eav\Model\ResourceModel\Helper */ protected $_resourceHelper; /** * @var \Magento\Framework\Validator\UniversalFactory */ protected $_universalFactory; /** * @var TransactionManagerInterface */ protected $transactionManager; /** * @var ObjectRelationProcessor */ protected $objectRelationProcessor; /** * @var UniqueValidationInterface */ private $uniqueValidator; /** * @param Context $context * @param array $data * @param UniqueValidationInterface|null $uniqueValidator * @param AttributeLoaderInterface|null $attributeLoader */ public function __construct( Context $context, $data = [], UniqueValidationInterface $uniqueValidator = null, AttributeLoaderInterface $attributeLoader = null ) { $this->_eavConfig = $context->getEavConfig(); $this->_resource = $context->getResource(); $this->_attrSetEntity = $context->getAttributeSetEntity(); $this->_localeFormat = $context->getLocaleFormat(); $this->_resourceHelper = $context->getResourceHelper(); $this->_universalFactory = $context->getUniversalFactory(); $this->transactionManager = $context->getTransactionManager(); $this->objectRelationProcessor = $context->getObjectRelationProcessor(); $this->uniqueValidator = $uniqueValidator ?: ObjectManager::getInstance()->get(UniqueValidationInterface::class); $this->attributeLoader = $attributeLoader ?: ObjectManager::getInstance()->get(AttributeLoaderInterface::class); parent::__construct(); $properties = get_object_vars($this); foreach ($data as $key => $value) { if (array_key_exists('_' . $key, $properties)) { $this->{'_' . $key} = $value; } } } /** * Set connections for entity operations * * @param \Magento\Framework\DB\Adapter\AdapterInterface|string $connection * @return $this * @codeCoverageIgnore */ public function setConnection($connection) { $this->connectionName = $connection; return $this; } /** * Resource initialization * * phpcs:disable Magento2.CodeAnalysis.EmptyBlock * * @return void */ protected function _construct() { } /** * Get connection * * @return \Magento\Framework\DB\Adapter\AdapterInterface * @codeCoverageIgnore */ public function getConnection() { return $this->_resource->getConnection(); } /** * For compatibility with AbstractModel * * @return string * @codeCoverageIgnore */ public function getIdFieldName() { return $this->getEntityIdField(); } /** * Retrieve table name * * @param string $alias * @return string * @codeCoverageIgnore */ public function getTable($alias) { return $this->_resource->getTableName($alias); } /** * Set configuration for the entity * * Accepts config node or name of entity type * * @param string|Type $type * @return $this */ public function setType($type) { $this->_type = $this->_eavConfig->getEntityType($type); return $this; } /** * Retrieve current entity config * * @return Type * @throws LocalizedException */ public function getEntityType() { if (empty($this->_type)) { throw new LocalizedException(__('Entity is not initialized')); } return $this->_type; } /** * Get entity type name * * @return string */ public function getType() { return $this->getEntityType()->getEntityTypeCode(); } /** * Get entity type id * * @return int */ public function getTypeId() { return (int) $this->getEntityType()->getEntityTypeId(); } /** * Unset attributes * * If NULL or not supplied removes configuration of all attributes * If string - removes only one, if array - all specified * * @param array|string|null $attributes * @return $this * @throws LocalizedException */ public function unsetAttributes($attributes = null) { if ($attributes === null) { $this->_attributesByCode = []; $this->_attributesByTable = []; return $this; } if (is_string($attributes)) { $attributes = [$attributes]; } if (!is_array($attributes)) { throw new LocalizedException(__('This parameter is unknown. Verify and try again.')); } foreach ($attributes as $attrCode) { if (!isset($this->_attributesByCode[$attrCode])) { continue; } $attr = $this->getAttribute($attrCode); unset($this->_attributesByTable[$attr->getBackend()->getTable()][$attrCode]); unset($this->_attributesByCode[$attrCode]); } return $this; } /** * Get EAV config model * * @return \Magento\Eav\Model\Config */ protected function _getConfig() { return $this->_eavConfig; } /** * Retrieve attribute instance by name, id or config node * * This will add the attribute configuration to entity's attributes cache * * If attribute is not found false is returned * * @param string|int|Element $attribute * @return AbstractAttribute|false * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function getAttribute($attribute) { /** @var $config \Magento\Eav\Model\Config */ $config = $this->_getConfig(); $attributeInstance = $config->getAttribute($this->getEntityType(), $attribute); if (!$attributeInstance->getAttributeCode() && in_array($attribute, $this->getDefaultAttributes(), true)) { $attributeInstance = clone $attributeInstance; $attributeInstance->setData([]); $attributeInstance->setAttributeCode( $attribute )->setBackendType( AbstractAttribute::TYPE_STATIC )->setIsGlobal( 1 )->setEntity( $this )->setEntityType( $this->getEntityType() )->setEntityTypeId( $this->getEntityType()->getId() ); } if (!$attributeInstance instanceof AbstractAttribute || (!$attributeInstance->getId() && !in_array($attributeInstance->getAttributeCode(), $this->getDefaultAttributes(), true)) ) { return false; } $this->addAttribute($attributeInstance); return $attributeInstance; } /** * Adding attribute to entity * * @param AbstractAttribute $attribute * @param DataObject|null $object * @return $this */ public function addAttribute(AbstractAttribute $attribute, $object = null) { $attribute->setEntity($this); $attributeCode = $attribute->getAttributeCode(); $this->_attributesByCode[$attributeCode] = $attribute; if ($object !== null) { $suffix = $this->getAttributesCacheSuffix($object); $this->attributesByScope[$suffix][$attributeCode] = $attribute; } if ($attribute->isStatic()) { $this->_staticAttributes[$attributeCode] = $attribute; } else { $this->_attributesByTable[$attribute->getBackendTable()][$attributeCode] = $attribute; } return $this; } /** * Get attributes by scope * * @param string $suffix * @return array */ private function getAttributesByScope($suffix) { return (isset($this->attributesByScope[$suffix]) && !empty($this->attributesByScope[$suffix])) ? $this->attributesByScope[$suffix] : $this->getAttributesByCode(); } /** * Get attributes cache suffix. * * @param DataObject $object * @return string */ private function getAttributesCacheSuffix(DataObject $object) { $attributeSetId = $object->getAttributeSetId() ?: 0; $storeId = $object->getStoreId() ?: 0; return $storeId . '-' . $attributeSetId; } /** * Retrieve partial load flag * * @param bool $flag * @return bool */ public function isPartialLoad($flag = null) { $result = $this->_isPartialLoad; if ($flag !== null) { $this->_isPartialLoad = (bool)$flag; } return $result; } /** * Retrieve partial save flag * * @param bool $flag * @return bool */ public function isPartialSave($flag = null) { $result = $this->_isPartialSave; if ($flag !== null) { $this->_isPartialSave = (bool) $flag; } return $result; } /** * Retrieve configuration for all attributes * * @param null|DataObject $object * @return $this */ public function loadAllAttributes($object = null) { return $this->attributeLoader->loadAllAttributes($this, $object); } /** * Retrieve sorted attributes * * @param int $setId * @return array */ public function getSortedAttributes($setId = null) { $attributes = $this->getAttributesByCode(); if ($setId === null) { $setId = $this->getEntityType()->getDefaultAttributeSetId(); } // initialize set info $this->_attrSetEntity->addSetInfo($this->getEntityType(), $attributes, $setId); foreach ($attributes as $code => $attribute) { /* @var $attribute AbstractAttribute */ if (!$attribute->isInSet($setId)) { unset($attributes[$code]); } } $this->_sortingSetId = $setId; uasort($attributes, [$this, 'attributesCompare']); return $attributes; } /** * Compare attributes * * @param Attribute $firstAttribute * @param Attribute $secondAttribute * @return int */ public function attributesCompare($firstAttribute, $secondAttribute) { $firstSort = $firstAttribute->getSortWeight((int) $this->_sortingSetId); $secondSort = $secondAttribute->getSortWeight((int) $this->_sortingSetId); return $firstSort <=> $secondSort; } /** * Check whether the attribute is Applicable to the object * * @param DataObject $object * @param AbstractAttribute $attribute * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _isApplicableAttribute($object, $attribute) { return true; } /** * Walk through the attributes and run method with optional arguments * * Returns array with results for each attribute * * if $partMethod is in format "part/method" will run method on specified part * for example: $this->walkAttributes('backend/validate'); * * @param string $partMethod * @param array $args * @param null|bool $collectExceptionMessages * * @throws \Exception|\Magento\Eav\Model\Entity\Attribute\Exception * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ public function walkAttributes($partMethod, array $args = [], $collectExceptionMessages = null) { $methodArr = explode('/', $partMethod); $part = ''; $method = ''; switch (count($methodArr)) { case 1: $part = 'attribute'; $method = $methodArr[0]; break; case 2: $part = $methodArr[0]; $method = $methodArr[1]; break; default: break; } $results = []; $suffix = $this->getAttributesCacheSuffix($args[0]); $instance = null; foreach ($this->getAttributesByScope($suffix) as $attrCode => $attribute) { if (isset($args[0]) && is_object($args[0]) && !$this->_isApplicableAttribute($args[0], $attribute)) { continue; } switch ($part) { case 'attribute': $instance = $attribute; break; case 'backend': $instance = $attribute->getBackend(); break; case 'frontend': $instance = $attribute->getFrontend(); break; case 'source': $instance = $attribute->getSource(); break; default: break; } if (!$this->_isCallableAttributeInstance($instance, $method, $args)) { continue; } try { // phpcs:disable Magento2.Functions.DiscouragedFunction $results[$attrCode] = call_user_func_array([$instance, $method], array_values($args)); } catch (\Magento\Eav\Model\Entity\Attribute\Exception $e) { if ($collectExceptionMessages) { $results[$attrCode] = $e->getMessage(); } else { throw $e; } } catch (\Exception $e) { if ($collectExceptionMessages) { $results[$attrCode] = $e->getMessage(); } else { /** @var \Magento\Eav\Model\Entity\Attribute\Exception $e */ $e = $this->_universalFactory->create( \Magento\Eav\Model\Entity\Attribute\Exception::class, ['phrase' => __($e->getMessage())] ); $e->setAttributeCode($attrCode)->setPart($part); throw $e; } } } return $results; } /** * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable * * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance * @param string $method * @param array $args array of arguments * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _isCallableAttributeInstance($instance, $method, $args) { if (!is_object($instance) || !method_exists($instance, $method) || !is_callable([$instance, $method])) { return false; } return true; } /** * Get attributes by name array * * @return array */ public function getAttributesByCode() { return $this->_attributesByCode; } /** * Get attributes by table and name array * * @return array */ public function getAttributesByTable() { return $this->_attributesByTable; } /** * Get entity table name * * @return string */ public function getEntityTable() { if (!$this->_entityTable) { $table = $this->getEntityType()->getEntityTable(); if (!$table) { $table = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE; } $this->_entityTable = $this->_resource->getTableName($table); } return $this->_entityTable; } /** * Get link id * * @return string * @since 100.1.0 */ public function getLinkField() { if (!$this->linkIdField) { $indexList = $this->getConnection()->getIndexList($this->getEntityTable()); $pkName = $this->getConnection()->getPrimaryKeyName($this->getEntityTable()); $this->linkIdField = $indexList[$pkName]['COLUMNS_LIST'][0] ?? null; if (!$this->linkIdField) { $this->linkIdField = $this->getEntityIdField(); } } return $this->linkIdField; } /** * Get entity id field name in entity table * * @return string */ public function getEntityIdField() { if (!$this->_entityIdField) { $this->_entityIdField = $this->getEntityType()->getEntityIdField(); if (!$this->_entityIdField) { $this->_entityIdField = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_ID_FIELD; } } return $this->_entityIdField; } /** * Get default entity id field name in attribute values tables * * @return string */ public function getValueEntityIdField() { return $this->getLinkField(); } /** * Get prefix for value tables * * @return string */ public function getValueTablePrefix() { if (!$this->_valueTablePrefix) { $prefix = (string) $this->getEntityType()->getValueTablePrefix(); if (!empty($prefix)) { $this->_valueTablePrefix = $prefix; /** * entity type prefix include DB table name prefix */ //$this->_resource->getTableName($prefix); } else { $this->_valueTablePrefix = $this->getEntityTable(); } } return $this->_valueTablePrefix; } /** * Get entity table prefix for value * * @return string */ public function getEntityTablePrefix() { if (empty($this->_entityTablePrefix)) { $prefix = $this->getEntityType()->getEntityTablePrefix(); if (empty($prefix)) { $prefix = $this->getEntityType()->getEntityTable(); if (empty($prefix)) { $prefix = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE; } } $this->_entityTablePrefix = $prefix; } return $this->_entityTablePrefix; } /** * Check whether the attribute is a real field in entity table * * @see \Magento\Eav\Model\Entity\AbstractEntity::getAttribute for $attribute format * @param int|string|AbstractAttribute $attribute * @return bool */ public function isAttributeStatic($attribute) { $attrInstance = $this->getAttribute($attribute); return $attrInstance && $attrInstance->getBackend()->isStatic(); } /** * Validate all object's attributes against configuration * * @param DataObject $object * @throws \Magento\Eav\Model\Entity\Attribute\Exception * @return true|array */ public function validate($object) { $this->loadAllAttributes($object); $result = $this->walkAttributes('backend/validate', [$object], $object->getCollectExceptionMessages()); $errors = []; foreach ($result as $attributeCode => $error) { if ($error === false) { $errors[$attributeCode] = true; } elseif (is_string($error)) { $errors[$attributeCode] = $error; } } if (!$errors) { return true; } return $errors; } /** * Set new increment id to object * * @param DataObject $object * @return $this */ public function setNewIncrementId(DataObject $object) { if ($object->getIncrementId()) { return $this; } $incrementId = $this->getEntityType()->fetchNewIncrementId($object->getStoreId()); if ($incrementId !== false) { $object->setIncrementId($incrementId); } return $this; } /** * Check attribute unique value * * @param AbstractAttribute $attribute * @param DataObject $object * @return bool */ public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object) { $connection = $this->getConnection(); $select = $connection->select(); $entityIdField = $this->getEntityIdField(); $attributeBackend = $attribute->getBackend(); if ($attributeBackend->getType() === 'static') { $value = $object->getData($attribute->getAttributeCode()); $bind = ['value' => $value !== null ? trim($value) : '']; $select->from( $this->getEntityTable(), $entityIdField )->where( $attribute->getAttributeCode() . ' = :value' ); } else { $value = $object->getData($attribute->getAttributeCode()); if ($attributeBackend->getType() == 'datetime') { $value = (new \DateTime($value))->format('Y-m-d H:i:s'); } $bind = [ 'attribute_id' => $attribute->getId(), 'value' => $value !== null ? trim($value) : '', ]; $entityIdField = $object->getResource()->getLinkField(); $select->from( $attributeBackend->getTable(), $entityIdField )->where( 'attribute_id = :attribute_id' )->where( 'value = :value' ); } if ($this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) { $bind['entity_type_id'] = $this->getTypeId(); $select->where('entity_type_id = :entity_type_id'); } $data = $connection->fetchCol($select, $bind); if ($object->getData($entityIdField)) { return $this->uniqueValidator->validate($attribute, $object, $this, $entityIdField, $data); } return !count($data); } /** * Retrieve default source model * * @return string */ public function getDefaultAttributeSourceModel() { return \Magento\Eav\Model\Entity::DEFAULT_SOURCE_MODEL; } /** * Load entity's attributes into the object * * @param AbstractModel $object * @param int $entityId * @param array|null $attributes * @return $this */ public function load($object, $entityId, $attributes = []) { \Magento\Framework\Profiler::start('EAV:load_entity'); /** * Load object base row data */ $object->beforeLoad($entityId); $select = $this->_getLoadRowSelect($object, $entityId); $row = $this->getConnection()->fetchRow($select); if (is_array($row) && !empty($row)) { $object->addData($row); $this->loadAttributesForObject($attributes, $object); $this->_loadModelAttributes($object); $this->_afterLoad($object); $object->afterLoad(); $object->setOrigData(); $object->setHasDataChanges(false); } else { $object->isObjectNew(true); } \Magento\Framework\Profiler::stop('EAV:load_entity'); return $this; } /** * Loads attributes metadata. * * @deprecated 101.0.0 Use self::loadAttributesForObject instead * @param array|null $attributes * @return $this * @since 100.1.0 */ protected function loadAttributesMetadata($attributes) { $this->loadAttributesForObject($attributes); return $this; } /** * Load model attributes data * * @param \Magento\Framework\Model\AbstractModel $object * @return $this */ protected function _loadModelAttributes($object) { if (!$object->getId()) { return $this; } \Magento\Framework\Profiler::start('load_model_attributes'); $selects = []; foreach (array_keys($this->getAttributesByTable()) as $table) { $attribute = current($this->_attributesByTable[$table]); $eavType = $attribute->getBackendType(); $select = $this->_getLoadAttributesSelect($object, $table); $selects[$eavType][] = $select->columns('*'); } $selectGroups = $this->_resourceHelper->getLoadAttributesSelectGroups($selects); foreach ($selectGroups as $selects) { if (!empty($selects)) { if (is_array($selects)) { $select = $this->_prepareLoadSelect($selects); } else { $select = $selects; } $values = $this->getConnection()->fetchAll($select); foreach ($values as $valueRow) { $this->_setAttributeValue($object, $valueRow); } } } \Magento\Framework\Profiler::stop('load_model_attributes'); return $this; } /** * Prepare select object for loading entity attributes values * * @param array $selects * @return \Magento\Framework\DB\Select */ protected function _prepareLoadSelect(array $selects) { return $this->getConnection()->select()->union($selects, \Magento\Framework\DB\Select::SQL_UNION_ALL); } /** * Retrieve select object for loading base entity row * * @param DataObject $object * @param string|int $rowId * @return \Magento\Framework\DB\Select * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _getLoadRowSelect($object, $rowId) { $select = $this->getConnection()->select()->from( $this->getEntityTable() )->where( $this->getEntityIdField() . ' =?', $rowId ); return $select; } /** * Retrieve select object for loading entity attributes values * * @param DataObject $object * @param string $table * @return \Magento\Framework\DB\Select */ protected function _getLoadAttributesSelect($object, $table) { $select = $this->getConnection()->select()->from( $table, [] )->where( $this->getEntityIdField() . ' =?', $object->getId() ); return $select; } /** * Initialize attribute value for object * * @param DataObject $object * @param array $valueRow * @return $this */ protected function _setAttributeValue($object, $valueRow) { $attribute = $this->getAttribute($valueRow['attribute_id']); if ($attribute) { $attributeCode = $attribute->getAttributeCode(); $object->setData($attributeCode, $valueRow['value']); $attribute->getBackend()->setEntityValueId($object, $valueRow['value_id']); } return $this; } /** * Save entity's attributes into the object's resource * * @param \Magento\Framework\Model\AbstractModel $object * @return $this * @throws \Exception * @throws AlreadyExistsException */ public function save(\Magento\Framework\Model\AbstractModel $object) { /** * Direct deleted items to delete method */ if ($object->isDeleted()) { return $this->delete($object); } if (!$object->hasDataChanges()) { return $this; } $this->beginTransaction(); try { $object->validateBeforeSave(); $object->beforeSave(); if ($object->isSaveAllowed()) { if (!$this->isPartialSave()) { $this->loadAllAttributes($object); } if ($this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE && !$object->getEntityTypeId() ) { $object->setEntityTypeId($this->getTypeId()); } $object->setParentId((int)$object->getParentId()); $this->objectRelationProcessor->validateDataIntegrity($this->getEntityTable(), $object->getData()); $this->_beforeSave($object); $this->processSave($object); $this->_afterSave($object); $object->afterSave(); } $this->addCommitCallback([$object, 'afterCommitCallback'])->commit(); $object->setHasDataChanges(false); } catch (DuplicateException $e) { $this->rollBack(); $object->setHasDataChanges(true); throw new AlreadyExistsException(__('Unique constraint violation found'), $e); } catch (\Exception $e) { $this->rollBack(); $object->setHasDataChanges(true); throw $e; } return $this; } /** * Save entity process * * @param \Magento\Framework\Model\AbstractModel $object * @return void * @since 100.1.0 */ protected function processSave($object) { $this->_processSaveData($this->_collectSaveData($object)); } /** * Retrieve Object instance with original data * * @param DataObject $object * @return DataObject */ protected function _getOrigObject($object) { $className = get_class($object); $origObject = $this->_universalFactory->create($className); $origObject->setData([]); $this->load($origObject, $object->getData($this->getEntityIdField())); return $origObject; } /** * Aggregate Data for attributes that will be deleted * * @param &array $delete * @param AbstractAttribute $attribute * @param AbstractEntity $object * @return void */ private function _aggregateDeleteData(&$delete, $attribute, $object) { foreach ($attribute->getBackend()->getAffectedFields($object) as $tableName => $valuesData) { if (!isset($delete[$tableName])) { $delete[$tableName] = []; } // phpcs:ignore Magento2.Performance.ForeachArrayMerge $delete[$tableName] = array_merge((array)$delete[$tableName], $valuesData); } } /** * Prepare entity object data for save * * Result array structure: * array ( * 'newObject', 'entityRow', 'insert', 'update', 'delete' * ) * * @param \Magento\Framework\Model\AbstractModel $newObject * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _collectSaveData($newObject) { $newData = $newObject->getData(); $entityId = $newObject->getData($this->getEntityIdField()); // define result data $entityRow = []; $insert = []; $update = []; $delete = []; if (!empty($entityId)) { $origData = $newObject->getOrigData(); /** * get current data in db for this entity if original data is empty */ if (empty($origData)) { $origData = $this->_getOrigObject($newObject)->getOrigData(); } if ($origData === null) { $origData = []; } /** * drop attributes that are unknown in new data * not needed after introduction of partial entity loading */ foreach ($origData as $k => $v) { if (!array_key_exists($k, $newData)) { unset($origData[$k]); } } } else { $origData = []; } $staticFields = $this->getConnection()->describeTable($this->getEntityTable()); $staticFields = array_keys($staticFields); $attributeCodes = array_keys($this->_attributesByCode); foreach ($newData as $k => $v) { /** * Check if data key is presented in static fields or attribute codes */ if (!in_array($k, $staticFields) && !in_array($k, $attributeCodes)) { continue; } $attribute = $this->getAttribute($k); if (empty($attribute)) { continue; } if (!$attribute->isInSet($newObject->getAttributeSetId()) && !in_array($k, $staticFields)) { $this->_aggregateDeleteData($delete, $attribute, $newObject); continue; } $attrId = $attribute->getAttributeId(); /** * Only scalar values can be stored in generic tables */ if (!$attribute->getBackend()->isScalar()) { continue; } /** * if attribute is static add to entity row and continue */ if ($this->isAttributeStatic($k)) { $entityRow[$k] = $this->_prepareStaticValue($k, $v); continue; } /** * Check comparability for attribute value */ if ($this->_canUpdateAttribute($attribute, $v, $origData)) { if ($this->_isAttributeValueEmpty($attribute, $v)) { $this->_aggregateDeleteData($delete, $attribute, $newObject); } elseif (!is_numeric($v) && $v !== $origData[$k] || is_numeric($v) && ($v != $origData[$k] || strlen($v) !== strlen($origData[$k])) ) { $update[$attrId] = [ 'value_id' => $attribute->getBackend()->getEntityValueId($newObject), 'value' => is_array($v) ? array_shift($v) : $v,//@TODO: MAGETWO-44182, ]; } } elseif (!$this->_isAttributeValueEmpty($attribute, $v)) { $insert[$attrId] = is_array($v) ? array_shift($v) : $v;//@TODO: MAGETWO-44182 } } $result = compact('newObject', 'entityRow', 'insert', 'update', 'delete'); return $result; } /** * Return if attribute exists in original data array. * * @param AbstractAttribute $attribute * @param mixed $v New value of the attribute. Can be used in subclasses. * @param array $origData * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _canUpdateAttribute(AbstractAttribute $attribute, $v, array &$origData) { return array_key_exists($attribute->getAttributeCode(), $origData); } /** * Retrieve static field properties * * @param string $field * @return array */ protected function _getStaticFieldProperties($field) { if (empty($this->_describeTable[$this->getEntityTable()])) { $this->_describeTable[$this->getEntityTable()] = $this->getConnection()->describeTable( $this->getEntityTable() ); } if (isset($this->_describeTable[$this->getEntityTable()][$field])) { return $this->_describeTable[$this->getEntityTable()][$field]; } return false; } /** * Prepare static value for save * * @param string $key * @param mixed $value * @return mixed */ protected function _prepareStaticValue($key, $value) { $fieldProp = $this->_getStaticFieldProperties($key); if (!$fieldProp) { return $value; } if ($fieldProp['DATA_TYPE'] == 'decimal') { $value = $this->_localeFormat->getNumber($value); } return $value; } /** * Save object collected data * * @param array $saveData array('newObject', 'entityRow', 'insert', 'update', 'delete') * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _processSaveData($saveData) { extract($saveData, EXTR_SKIP); /** * Import variables into the current symbol table from save data array * * @see \Magento\Eav\Model\Entity\AbstractEntity::_collectSaveData() * * @var array $entityRow * @var \Magento\Framework\Model\AbstractModel $newObject * @var array $insert * @var array $update * @var array $delete */ $connection = $this->getConnection(); $insertEntity = true; $entityTable = $this->getEntityTable(); $entityIdField = $this->getEntityIdField(); // phpstan:ignore "Undefined variable" $entityId = $newObject->getId(); // phpstan:ignore "Undefined variable" unset($entityRow[$entityIdField]); if (!empty($entityId) && is_numeric($entityId)) { $bind = ['entity_id' => $entityId]; $select = $connection->select()->from($entityTable, $entityIdField)->where("{$entityIdField} = :entity_id"); $result = $connection->fetchOne($select, $bind); if ($result) { $insertEntity = false; } } else { $entityId = null; } /** * Process base row */ // phpstan:ignore "Undefined variable" $entityObject = new DataObject($entityRow); $entityRow = $this->_prepareDataForTable($entityObject, $entityTable); if ($insertEntity) { if (!empty($entityId)) { $entityRow[$entityIdField] = $entityId; $connection->insertForce($entityTable, $entityRow); } else { $connection->insert($entityTable, $entityRow); $entityId = $connection->lastInsertId($entityTable); } // phpstan:ignore "Undefined variable" $newObject->setId($entityId); } else { $where = sprintf('%s=%d', $connection->quoteIdentifier($entityIdField), $entityId); $connection->update($entityTable, $entityRow, $where); } /** * insert attribute values */ if (!empty($insert)) { foreach ($insert as $attributeId => $value) { $attribute = $this->getAttribute($attributeId); // phpstan:ignore "Undefined variable" $this->_insertAttribute($newObject, $attribute, $value); } } /** * update attribute values */ if (!empty($update)) { foreach ($update as $attributeId => $v) { $attribute = $this->getAttribute($attributeId); // phpstan:ignore "Undefined variable" $this->_updateAttribute($newObject, $attribute, $v['value_id'], $v['value']); } } /** * delete empty attribute values */ if (!empty($delete)) { foreach ($delete as $table => $values) { // phpstan:ignore "Undefined variable" $this->_deleteAttributes($newObject, $table, $values); } } $this->_processAttributeValues(); // phpstan:ignore "Undefined variable" $newObject->isObjectNew(false); return $this; } /** * Insert entity attribute value * * @param DataObject $object * @param AbstractAttribute $attribute * @param mixed $value * @return $this */ protected function _insertAttribute($object, $attribute, $value) { return $this->_saveAttribute($object, $attribute, $value); } /** * Update entity attribute value * * @param DataObject $object * @param AbstractAttribute $attribute * @param mixed $valueId * @param mixed $value * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _updateAttribute($object, $attribute, $valueId, $value) { return $this->_saveAttribute($object, $attribute, $value); } /** * Save entity attribute value * * Collect for mass save * * @param \Magento\Framework\Model\AbstractModel $object * @param AbstractAttribute $attribute * @param mixed $value * @return $this */ protected function _saveAttribute($object, $attribute, $value) { $table = $attribute->getBackend()->getTable(); if (!isset($this->_attributeValuesToSave[$table])) { $this->_attributeValuesToSave[$table] = []; } $entityIdField = $attribute->getBackend()->getEntityIdField(); $data = [ $entityIdField => $object->getId(), 'attribute_id' => $attribute->getId(), 'value' => $this->_prepareValueForSave($value, $attribute), ]; if (!$this->getEntityTable() || $this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) { $data['entity_type_id'] = $object->getEntityTypeId(); } $this->_attributeValuesToSave[$table][] = $data; return $this; } /** * Save and delete collected attribute values * * @return $this */ protected function _processAttributeValues() { $connection = $this->getConnection(); foreach ($this->_attributeValuesToSave as $table => $data) { $connection->insertOnDuplicate($table, $data, array_keys($data[0])); } foreach ($this->_attributeValuesToDelete as $table => $valueIds) { $connection->delete($table, ['value_id IN (?)' => $valueIds]); } // reset data arrays $this->_attributeValuesToSave = []; $this->_attributeValuesToDelete = []; return $this; } /** * Prepare value for save * * @param mixed $value * @param AbstractAttribute $attribute * @return mixed */ protected function _prepareValueForSave($value, AbstractAttribute $attribute) { $type = $attribute->getBackendType(); if (($type == 'int' || $type == 'decimal' || $type == 'datetime') && $value === '') { $value = null; } elseif ($type == 'decimal') { $value = $this->_localeFormat->getNumber($value); } $backendTable = $attribute->getBackendTable(); if (!isset(self::$_attributeBackendTables[$backendTable])) { self::$_attributeBackendTables[$backendTable] = $this->getConnection()->describeTable($backendTable); } $describe = self::$_attributeBackendTables[$backendTable]; $columnName = $attribute->isStatic() ? $attribute->getAttributeCode() : 'value'; return $this->getConnection()->prepareColumnValue($describe[$columnName], $value); } /** * Delete entity attribute values * * @param DataObject $object * @param string $table * @param array $info * @return DataObject * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _deleteAttributes($object, $table, $info) { $valueIds = []; foreach ($info as $itemData) { $valueIds[] = $itemData['value_id']; } if (empty($valueIds)) { return $this; } if (isset($this->_attributeValuesToDelete[$table])) { $this->_attributeValuesToDelete[$table] = array_merge($this->_attributeValuesToDelete[$table], $valueIds); } else { $this->_attributeValuesToDelete[$table] = $valueIds; } return $this; } /** * Save attribute * * @param DataObject $object * @param string $attributeCode * @return $this * @throws \Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function saveAttribute(DataObject $object, $attributeCode) { $attribute = $this->getAttribute($attributeCode); $backend = $attribute->getBackend(); $table = $backend->getTable(); $entity = $attribute->getEntity(); $connection = $this->getConnection(); $row = $this->getAttributeRow($entity, $object, $attribute); $newValue = $object->getData($attributeCode); if ($attribute->isValueEmpty($newValue)) { $newValue = null; } $whereArr = []; foreach ($row as $field => $value) { $whereArr[] = $connection->quoteInto($field . '=?', $value); } $where = implode(' AND ', $whereArr); $connection->beginTransaction(); try { $select = $connection->select()->from($table, ['value_id', 'value'])->where($where); $origRow = $connection->fetchRow($select); $origValueId = $origRow['value_id'] ?? false; $origValue = $origRow['value'] ?? null; if ($origValueId === false && $newValue !== null) { $this->_insertAttribute($object, $attribute, $newValue); } elseif ($origValueId !== false && $newValue !== null) { $this->_updateAttribute($object, $attribute, $origValueId, $newValue); } elseif ($origValueId !== false && $newValue === null && $origValue !== null) { $connection->delete($table, $where); } $this->_processAttributeValues(); $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); throw $e; } return $this; } /** * Return attribute row to prepare where statement * * @param DataObject $entity * @param DataObject $object * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute * @return array */ protected function getAttributeRow($entity, $object, $attribute) { $data = [ 'attribute_id' => $attribute->getId(), $this->getLinkField() => $object->getData($this->getLinkField()), ]; if (!$this->getEntityTable()) { $data['entity_type_id'] = $entity->getTypeId(); } return $data; } /** * Delete entity using current object's data * * @param DataObject|int|string $object * @return $this * @throws \Exception * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function delete($object) { try { $connection = $this->transactionManager->start($this->getConnection()); $id = 0; if (is_numeric($object)) { $id = (int) $object; } elseif ($object instanceof \Magento\Framework\Model\AbstractModel) { $object->beforeDelete(); $id = (int) $object->getData($this->getLinkField()); } $this->_beforeDelete($object); $this->evaluateDelete( $object, $id, $connection ); $this->_afterDelete($object); if ($object instanceof \Magento\Framework\Model\AbstractModel) { $object->isDeleted(true); $object->afterDelete(); } $this->transactionManager->commit(); if ($object instanceof \Magento\Framework\Model\AbstractModel) { $object->afterDeleteCommit(); } } catch (\Exception $e) { $this->transactionManager->rollBack(); throw $e; } return $this; } /** * Evaluate Delete operations * * @param DataObject|int|string $object * @param string|int $id * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return void * @throws \Exception * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @since 100.1.0 */ protected function evaluateDelete($object, $id, $connection) { $where = [$this->getEntityIdField() . '=?' => $id]; $this->objectRelationProcessor->delete( $this->transactionManager, $connection, $this->getEntityTable(), $this->getConnection()->quoteInto( $this->getEntityIdField() . '=?', $id ), [$this->getEntityIdField() => $id] ); $this->loadAllAttributes($object); foreach ($this->getAttributesByTable() as $table => $attributes) { $this->getConnection()->delete( $table, $where ); } } /** * After Load Entity process * * @param DataObject $object * @return $this */ protected function _afterLoad(DataObject $object) { \Magento\Framework\Profiler::start('after_load'); $this->walkAttributes('backend/afterLoad', [$object]); \Magento\Framework\Profiler::stop('after_load'); return $this; } /** * Before delete Entity process * * @param DataObject $object * @return $this */ protected function _beforeSave(DataObject $object) { $this->walkAttributes('backend/beforeSave', [$object]); return $this; } /** * After Save Entity process * * @param DataObject $object * @return $this */ protected function _afterSave(DataObject $object) { $this->walkAttributes('backend/afterSave', [$object]); return $this; } /** * Before Delete Entity process * * @param DataObject $object * @return $this */ protected function _beforeDelete(DataObject $object) { $this->walkAttributes('backend/beforeDelete', [$object]); return $this; } /** * After delete entity process * * @param DataObject $object * @return $this */ protected function _afterDelete(DataObject $object) { $this->walkAttributes('backend/afterDelete', [$object]); return $this; } /** * Retrieve Default attribute model * * @return string */ protected function _getDefaultAttributeModel() { return \Magento\Eav\Model\Entity::DEFAULT_ATTRIBUTE_MODEL; } /** * Retrieve default entity attributes * * @return string[] */ protected function _getDefaultAttributes() { return ['entity_type_id', 'attribute_set_id', 'created_at', 'updated_at', 'parent_id', 'increment_id']; } /** * Retrieve default entity static attributes * * @return string[] */ public function getDefaultAttributes() { return array_unique( array_merge( $this->_getDefaultAttributes(), [$this->getEntityIdField(), $this->getLinkField()] ) ); } /** * Check is attribute value empty * * @param AbstractAttribute $attribute * @param mixed $value * @return bool */ protected function _isAttributeValueEmpty(AbstractAttribute $attribute, $value) { return $attribute->isValueEmpty($value); } /** * The getter function to get the AttributeLoaderInterface * * @return AttributeLoaderInterface * * @deprecated 100.1.0 * @since 100.1.0 */ protected function getAttributeLoader() { return $this->attributeLoader; } /** * Perform actions after entity load * * @param DataObject $object * @since 100.1.0 */ public function afterLoad(DataObject $object) { $this->_afterLoad($object); } /** * Perform actions before entity save * * @param DataObject $object * @since 100.1.0 */ public function beforeSave(DataObject $object) { $this->_beforeSave($object); } /** * Perform actions after entity save * * @param DataObject $object * @since 100.1.0 */ public function afterSave(DataObject $object) { $this->_afterSave($object); } /** * Perform actions before entity delete * * @param DataObject $object * @since 100.1.0 */ public function beforeDelete(DataObject $object) { $this->_beforeDelete($object); } /** * Perform actions after entity delete * * @param DataObject $object * @since 100.1.0 */ public function afterDelete(DataObject $object) { $this->_afterDelete($object); } /** * Load attributes for object * * If the object will not pass all attributes for this entity type will be loaded * * @param array $attributes * @param AbstractEntity|null $object * @return void * @since 101.0.0 */ protected function loadAttributesForObject($attributes, $object = null) { if (empty($attributes)) { $this->loadAllAttributes($object); } else { if (!is_array($attributes)) { $attributes = [$attributes]; } foreach ($attributes as $attrCode) { $this->getAttribute($attrCode); } } } }