| Current Path : /var/www/clients/client3/web2/web/vendor/magento/module-eav/Model/Entity/ |
| Current File : /var/www/clients/client3/web2/web/vendor/magento/module-eav/Model/Entity/AbstractEntity.php |
<?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 <core@magentocommerce.com>
* @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;
/**
* 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 = [];
/**
* Entity table
*
* @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
*/
public function __construct(
Context $context,
$data = [],
UniqueValidationInterface $uniqueValidator = 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);
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->getAttributeLoader()->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], $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' => 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' => 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()
{
if ($this->attributeLoader === null) {
$this->attributeLoader= ObjectManager::getInstance()->get(AttributeLoaderInterface::class);
}
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);
}
}
}
}