AttributePersistor.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Eav\Model\ResourceModel;
  7. use Magento\Catalog\Model\Product;
  8. use Magento\Eav\Api\AttributeRepositoryInterface;
  9. use Magento\Catalog\Api\ProductRepositoryInterface;
  10. use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
  11. use Magento\Framework\EntityManager\EntityMetadataInterface;
  12. use Magento\Store\Model\StoreManagerInterface;
  13. use Magento\Framework\Locale\FormatInterface;
  14. use Magento\Framework\Model\Entity\ScopeInterface;
  15. use Magento\Framework\EntityManager\MetadataPool;
  16. /**
  17. * Class AttributePersistor
  18. */
  19. class AttributePersistor
  20. {
  21. /**
  22. * @var AttributeRepositoryInterface
  23. */
  24. private $attributeRepository;
  25. /**
  26. * @var FormatInterface
  27. */
  28. private $localeFormat;
  29. /**
  30. * @var MetadataPool
  31. */
  32. private $metadataPool;
  33. /**
  34. * @var array
  35. */
  36. private $insert = [];
  37. /**
  38. * @var array
  39. */
  40. private $update = [];
  41. /**
  42. * @var array
  43. */
  44. private $delete = [];
  45. /**
  46. * @param FormatInterface $localeFormat
  47. * @param AttributeRepositoryInterface $attributeRepository
  48. * @param MetadataPool $metadataPool
  49. */
  50. public function __construct(
  51. FormatInterface $localeFormat,
  52. AttributeRepositoryInterface $attributeRepository,
  53. MetadataPool $metadataPool
  54. ) {
  55. $this->attributeRepository = $attributeRepository;
  56. $this->metadataPool = $metadataPool;
  57. $this->localeFormat = $localeFormat;
  58. }
  59. /**
  60. * @param string $entityType
  61. * @param int $link
  62. * @param string $attributeCode
  63. * @return void
  64. */
  65. public function registerDelete($entityType, $link, $attributeCode)
  66. {
  67. $this->delete[$entityType][$link][$attributeCode] = null;
  68. }
  69. /**
  70. * @param string $entityType
  71. * @param int $link
  72. * @param string $attributeCode
  73. * @param mixed $value
  74. * @return void
  75. */
  76. public function registerUpdate($entityType, $link, $attributeCode, $value)
  77. {
  78. $this->update[$entityType][$link][$attributeCode] = $value;
  79. }
  80. /**
  81. * @param string $entityType
  82. * @param int $link
  83. * @param string $attributeCode
  84. * @param mixed $value
  85. * @return void
  86. */
  87. public function registerInsert($entityType, $link, $attributeCode, $value)
  88. {
  89. $this->insert[$entityType][$link][$attributeCode] = $value;
  90. }
  91. /**
  92. * @param string $entityType
  93. * @param \Magento\Framework\Model\Entity\ScopeInterface[] $context
  94. * @return void
  95. * @throws \Exception
  96. * @throws \Magento\Framework\Exception\LocalizedException
  97. */
  98. public function processDeletes($entityType, $context)
  99. {
  100. if (!isset($this->delete[$entityType]) || !is_array($this->delete[$entityType])) {
  101. return;
  102. }
  103. $metadata = $this->metadataPool->getMetadata($entityType);
  104. foreach ($this->delete[$entityType] as $link => $data) {
  105. $attributeCodes = array_keys($data);
  106. foreach ($attributeCodes as $attributeCode) {
  107. /** @var AbstractAttribute $attribute */
  108. $attribute = $this->attributeRepository->get($metadata->getEavEntityType(), $attributeCode);
  109. $conditions = $this->buildDeleteConditions($attribute, $metadata, $context, $link);
  110. foreach ($conditions as $condition) {
  111. $metadata->getEntityConnection()->delete(
  112. $attribute->getBackend()->getTable(),
  113. $condition
  114. );
  115. }
  116. }
  117. }
  118. }
  119. /**
  120. * @param string $entityType
  121. * @param \Magento\Framework\Model\Entity\ScopeInterface[] $context
  122. * @return void
  123. * @throws \Exception
  124. * @throws \Magento\Framework\Exception\LocalizedException
  125. */
  126. public function processInserts($entityType, $context)
  127. {
  128. if (!isset($this->insert[$entityType]) || !is_array($this->insert[$entityType])) {
  129. return;
  130. }
  131. $metadata = $this->metadataPool->getMetadata($entityType);
  132. $insertData = $this->prepareInsertDataForMultipleSave($entityType, $context);
  133. foreach ($insertData as $table => $tableData) {
  134. foreach ($tableData as $data) {
  135. $metadata->getEntityConnection()->insertArray(
  136. $table,
  137. $data['columns'],
  138. $data['data'],
  139. \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_IGNORE
  140. );
  141. }
  142. }
  143. }
  144. /**
  145. * Prepare data for insert multiple rows
  146. *
  147. * @param string $entityType
  148. * @param \Magento\Framework\Model\Entity\ScopeInterface[] $context
  149. * @return array
  150. */
  151. private function prepareInsertDataForMultipleSave($entityType, $context)
  152. {
  153. $metadata = $this->metadataPool->getMetadata($entityType);
  154. $insertData = [];
  155. foreach ($this->insert[$entityType] as $link => $data) {
  156. foreach ($data as $attributeCode => $attributeValue) {
  157. /** @var AbstractAttribute $attribute */
  158. $attribute = $this->attributeRepository->get(
  159. $metadata->getEavEntityType(),
  160. $attributeCode
  161. );
  162. $attributeTable = $attribute->getBackend()->getTable();
  163. $conditions = $this->buildInsertConditions($attribute, $metadata, $context, $link);
  164. $value = $this->prepareValue($entityType, $attributeValue, $attribute);
  165. foreach ($conditions as $condition) {
  166. $condition['value'] = $value;
  167. $columns = array_keys($condition);
  168. $columnsHash = implode('', $columns);
  169. $insertData[$attributeTable][$columnsHash]['columns'] = $columns;
  170. $insertData[$attributeTable][$columnsHash]['data'][] = array_values($condition);
  171. }
  172. }
  173. }
  174. return $insertData;
  175. }
  176. /**
  177. * @param string $entityType
  178. * @param \Magento\Framework\Model\Entity\ScopeInterface[] $context
  179. * @return void
  180. * @throws \Exception
  181. * @throws \Magento\Framework\Exception\LocalizedException
  182. */
  183. public function processUpdates($entityType, $context)
  184. {
  185. if (!isset($this->update[$entityType]) || !is_array($this->update[$entityType])) {
  186. return;
  187. }
  188. $metadata = $this->metadataPool->getMetadata($entityType);
  189. foreach ($this->update[$entityType] as $link => $data) {
  190. foreach ($data as $attributeCode => $attributeValue) {
  191. /** @var AbstractAttribute $attribute */
  192. $attribute = $this->attributeRepository->get(
  193. $metadata->getEavEntityType(),
  194. $attributeCode
  195. );
  196. $conditions = $this->buildUpdateConditions($attribute, $metadata, $context, $link);
  197. foreach ($conditions as $condition) {
  198. $metadata->getEntityConnection()->update(
  199. $attribute->getBackend()->getTable(),
  200. [
  201. 'value' => $this->prepareValue($entityType, $attributeValue, $attribute)
  202. ],
  203. $condition
  204. );
  205. }
  206. }
  207. }
  208. }
  209. /**
  210. * Builds set of update conditions (WHERE clause)
  211. *
  212. * @param AbstractAttribute $attribute
  213. * @param EntityMetadataInterface $metadata
  214. * @param ScopeInterface[] $scopes
  215. * @param string $linkFieldValue
  216. * @return array
  217. */
  218. protected function buildUpdateConditions(
  219. AbstractAttribute $attribute,
  220. EntityMetadataInterface $metadata,
  221. array $scopes,
  222. $linkFieldValue
  223. ) {
  224. $condition = [
  225. $metadata->getLinkField() . ' = ?' => $linkFieldValue,
  226. 'attribute_id = ?' => $attribute->getAttributeId(),
  227. ];
  228. foreach ($scopes as $scope) {
  229. $identifier = $metadata->getEntityConnection()->quoteIdentifier($scope->getIdentifier());
  230. $condition[$identifier . ' = ?'] = $this->getScopeValue($scope, $attribute);
  231. }
  232. return [
  233. $condition,
  234. ];
  235. }
  236. /**
  237. * Builds set of delete conditions (WHERE clause)
  238. *
  239. * @param AbstractAttribute $attribute
  240. * @param EntityMetadataInterface $metadata
  241. * @param ScopeInterface[] $scopes
  242. * @param string $linkFieldValue
  243. * @return array
  244. */
  245. protected function buildDeleteConditions(
  246. AbstractAttribute $attribute,
  247. EntityMetadataInterface $metadata,
  248. array $scopes,
  249. $linkFieldValue
  250. ) {
  251. $condition = [
  252. $metadata->getLinkField() . ' = ?' => $linkFieldValue,
  253. 'attribute_id = ?' => $attribute->getAttributeId(),
  254. ];
  255. foreach ($scopes as $scope) {
  256. $identifier = $metadata->getEntityConnection()->quoteIdentifier($scope->getIdentifier());
  257. $condition[$identifier . ' = ?'] = $this->getScopeValue($scope, $attribute);
  258. }
  259. return [
  260. $condition,
  261. ];
  262. }
  263. /**
  264. * Builds set of insert conditions
  265. *
  266. * @param AbstractAttribute $attribute
  267. * @param EntityMetadataInterface $metadata
  268. * @param ScopeInterface[] $scopes
  269. * @param string $linkFieldValue
  270. * @return array
  271. */
  272. protected function buildInsertConditions(
  273. AbstractAttribute $attribute,
  274. EntityMetadataInterface $metadata,
  275. array $scopes,
  276. $linkFieldValue
  277. ) {
  278. $condition = [
  279. $metadata->getLinkField() => $linkFieldValue,
  280. 'attribute_id' => $attribute->getAttributeId(),
  281. ];
  282. foreach ($scopes as $scope) {
  283. $condition[$scope->getIdentifier()] = $this->getScopeValue($scope, $attribute);
  284. }
  285. return [
  286. $condition,
  287. ];
  288. }
  289. /**
  290. * Flush attributes to storage
  291. *
  292. * @param string $entityType
  293. * @param ScopeInterface[] $context
  294. * @return void
  295. */
  296. public function flush($entityType, $context)
  297. {
  298. $this->processDeletes($entityType, $context);
  299. $this->processInserts($entityType, $context);
  300. $this->processUpdates($entityType, $context);
  301. unset($this->delete, $this->insert, $this->update);
  302. }
  303. /**
  304. * @param string $entityType
  305. * @param string $value
  306. * @param AbstractAttribute $attribute
  307. * @return mixed
  308. * @throws \Exception
  309. */
  310. protected function prepareValue($entityType, $value, AbstractAttribute $attribute)
  311. {
  312. $metadata = $this->metadataPool->getMetadata($entityType);
  313. $type = $attribute->getBackendType();
  314. if (($type == 'int' || $type == 'decimal' || $type == 'datetime') && $value === '') {
  315. $value = null;
  316. } elseif ($type == 'decimal') {
  317. $value = $this->localeFormat->getNumber($value);
  318. } elseif ($type == 'varchar' && is_array($value)) {
  319. $value = implode(',', $value);
  320. }
  321. $describe = $metadata->getEntityConnection()->describeTable($attribute->getBackendTable());
  322. return $metadata->getEntityConnection()->prepareColumnValue($describe['value'], $value);
  323. }
  324. /**
  325. * @param ScopeInterface $scope
  326. * @param AbstractAttribute $attribute
  327. * @param bool $useDefault
  328. * @return string
  329. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  330. */
  331. protected function getScopeValue(ScopeInterface $scope, AbstractAttribute $attribute, $useDefault = false)
  332. {
  333. if ($useDefault && $scope->getFallback()) {
  334. return $this->getScopeValue($scope->getFallback(), $attribute, $useDefault);
  335. }
  336. return $scope->getValue();
  337. }
  338. }