AbstractEntity.php 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Eav\Model\Entity;
  7. use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
  8. use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
  9. use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
  10. use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
  11. use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
  12. use Magento\Framework\App\Config\Element;
  13. use Magento\Framework\DataObject;
  14. use Magento\Framework\DB\Adapter\DuplicateException;
  15. use Magento\Framework\Exception\AlreadyExistsException;
  16. use Magento\Framework\Exception\LocalizedException;
  17. use Magento\Framework\Model\AbstractModel;
  18. use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
  19. use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
  20. use Magento\Eav\Model\ResourceModel\Attribute\DefaultEntityAttributes\ProviderInterface as DefaultAttributesProvider;
  21. use Magento\Framework\Model\ResourceModel\AbstractResource;
  22. use Magento\Framework\App\ObjectManager;
  23. /**
  24. * Entity/Attribute/Model - entity abstract
  25. *
  26. * @api
  27. * @author Magento Core Team <core@magentocommerce.com>
  28. * @SuppressWarnings(PHPMD.TooManyFields)
  29. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  30. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  31. * @since 100.0.2
  32. */
  33. abstract class AbstractEntity extends AbstractResource implements EntityInterface, DefaultAttributesProvider
  34. {
  35. /**
  36. * @var \Magento\Eav\Model\Entity\AttributeLoaderInterface
  37. * @since 100.1.0
  38. */
  39. protected $attributeLoader;
  40. /**
  41. * Connection name
  42. *
  43. * @var string
  44. */
  45. protected $connectionName;
  46. /**
  47. * Entity type configuration
  48. *
  49. * @var Type
  50. */
  51. protected $_type;
  52. /**
  53. * Attributes array by attribute name
  54. *
  55. * @var array
  56. */
  57. protected $_attributesByCode = [];
  58. /**
  59. * Attributes stored by scope (store id and attribute set id).
  60. *
  61. * @var array
  62. */
  63. private $attributesByScope = [];
  64. /**
  65. * Two-dimensional array by table name and attribute name
  66. *
  67. * @var array
  68. */
  69. protected $_attributesByTable = [];
  70. /**
  71. * Attributes that are static fields in entity table
  72. *
  73. * @var array
  74. */
  75. protected $_staticAttributes = [];
  76. /**
  77. * Entity table
  78. *
  79. * @var string
  80. */
  81. protected $_entityTable;
  82. /**
  83. * Describe data for tables
  84. *
  85. * @var array
  86. */
  87. protected $_describeTable = [];
  88. /**
  89. * Entity table identification field name
  90. *
  91. * @var string
  92. */
  93. protected $_entityIdField;
  94. /**
  95. * Entity primary key for link field name
  96. *
  97. * @var string
  98. * @since 100.1.0
  99. */
  100. protected $linkIdField;
  101. /**
  102. * Entity values table identification field name
  103. *
  104. * @var string
  105. */
  106. protected $_valueEntityIdField;
  107. /**
  108. * Entity value table prefix
  109. *
  110. * @var string
  111. */
  112. protected $_valueTablePrefix;
  113. /**
  114. * Entity table string
  115. *
  116. * @var string
  117. */
  118. protected $_entityTablePrefix;
  119. /**
  120. * Partial load flag
  121. *
  122. * @var bool
  123. */
  124. protected $_isPartialLoad = false;
  125. /**
  126. * Partial save flag
  127. *
  128. * @var bool
  129. */
  130. protected $_isPartialSave = false;
  131. /**
  132. * Attribute set id which used for get sorted attributes
  133. *
  134. * @var int
  135. */
  136. protected $_sortingSetId = null;
  137. /**
  138. * Entity attribute values per backend table to delete
  139. *
  140. * @var array
  141. */
  142. protected $_attributeValuesToDelete = [];
  143. /**
  144. * Entity attribute values per backend table to save
  145. *
  146. * @var array
  147. */
  148. protected $_attributeValuesToSave = [];
  149. /**
  150. * Array of describe attribute backend tables
  151. * The table name as key
  152. *
  153. * @var array
  154. */
  155. protected static $_attributeBackendTables = [];
  156. /**
  157. * @var \Magento\Framework\App\ResourceConnection
  158. */
  159. protected $_resource;
  160. /**
  161. * @var \Magento\Eav\Model\Config
  162. */
  163. protected $_eavConfig;
  164. /**
  165. * @var \Magento\Eav\Model\Entity\Attribute\Set
  166. */
  167. protected $_attrSetEntity;
  168. /**
  169. * @var \Magento\Framework\Locale\FormatInterface
  170. */
  171. protected $_localeFormat;
  172. /**
  173. * @var \Magento\Eav\Model\ResourceModel\Helper
  174. */
  175. protected $_resourceHelper;
  176. /**
  177. * @var \Magento\Framework\Validator\UniversalFactory
  178. */
  179. protected $_universalFactory;
  180. /**
  181. * @var TransactionManagerInterface
  182. */
  183. protected $transactionManager;
  184. /**
  185. * @var ObjectRelationProcessor
  186. */
  187. protected $objectRelationProcessor;
  188. /**
  189. * @var UniqueValidationInterface
  190. */
  191. private $uniqueValidator;
  192. /**
  193. * @param Context $context
  194. * @param array $data
  195. * @param UniqueValidationInterface|null $uniqueValidator
  196. */
  197. public function __construct(
  198. Context $context,
  199. $data = [],
  200. UniqueValidationInterface $uniqueValidator = null
  201. ) {
  202. $this->_eavConfig = $context->getEavConfig();
  203. $this->_resource = $context->getResource();
  204. $this->_attrSetEntity = $context->getAttributeSetEntity();
  205. $this->_localeFormat = $context->getLocaleFormat();
  206. $this->_resourceHelper = $context->getResourceHelper();
  207. $this->_universalFactory = $context->getUniversalFactory();
  208. $this->transactionManager = $context->getTransactionManager();
  209. $this->objectRelationProcessor = $context->getObjectRelationProcessor();
  210. $this->uniqueValidator = $uniqueValidator ?:
  211. ObjectManager::getInstance()->get(UniqueValidationInterface::class);
  212. parent::__construct();
  213. $properties = get_object_vars($this);
  214. foreach ($data as $key => $value) {
  215. if (array_key_exists('_' . $key, $properties)) {
  216. $this->{'_' . $key} = $value;
  217. }
  218. }
  219. }
  220. /**
  221. * Set connections for entity operations
  222. *
  223. * @param \Magento\Framework\DB\Adapter\AdapterInterface|string $connection
  224. * @return $this
  225. * @codeCoverageIgnore
  226. */
  227. public function setConnection($connection)
  228. {
  229. $this->connectionName = $connection;
  230. return $this;
  231. }
  232. /**
  233. * Resource initialization
  234. *
  235. * @return void
  236. */
  237. protected function _construct()
  238. {
  239. }
  240. /**
  241. * Get connection
  242. *
  243. * @return \Magento\Framework\DB\Adapter\AdapterInterface
  244. * @codeCoverageIgnore
  245. */
  246. public function getConnection()
  247. {
  248. return $this->_resource->getConnection();
  249. }
  250. /**
  251. * For compatibility with AbstractModel
  252. *
  253. * @return string
  254. * @codeCoverageIgnore
  255. */
  256. public function getIdFieldName()
  257. {
  258. return $this->getEntityIdField();
  259. }
  260. /**
  261. * Retrieve table name
  262. *
  263. * @param string $alias
  264. * @return string
  265. * @codeCoverageIgnore
  266. */
  267. public function getTable($alias)
  268. {
  269. return $this->_resource->getTableName($alias);
  270. }
  271. /**
  272. * Set configuration for the entity
  273. *
  274. * Accepts config node or name of entity type
  275. *
  276. * @param string|Type $type
  277. * @return $this
  278. */
  279. public function setType($type)
  280. {
  281. $this->_type = $this->_eavConfig->getEntityType($type);
  282. return $this;
  283. }
  284. /**
  285. * Retrieve current entity config
  286. *
  287. * @return Type
  288. * @throws LocalizedException
  289. */
  290. public function getEntityType()
  291. {
  292. if (empty($this->_type)) {
  293. throw new LocalizedException(__('Entity is not initialized'));
  294. }
  295. return $this->_type;
  296. }
  297. /**
  298. * Get entity type name
  299. *
  300. * @return string
  301. */
  302. public function getType()
  303. {
  304. return $this->getEntityType()->getEntityTypeCode();
  305. }
  306. /**
  307. * Get entity type id
  308. *
  309. * @return int
  310. */
  311. public function getTypeId()
  312. {
  313. return (int) $this->getEntityType()->getEntityTypeId();
  314. }
  315. /**
  316. * Unset attributes
  317. *
  318. * If NULL or not supplied removes configuration of all attributes
  319. * If string - removes only one, if array - all specified
  320. *
  321. * @param array|string|null $attributes
  322. * @return $this
  323. * @throws LocalizedException
  324. */
  325. public function unsetAttributes($attributes = null)
  326. {
  327. if ($attributes === null) {
  328. $this->_attributesByCode = [];
  329. $this->_attributesByTable = [];
  330. return $this;
  331. }
  332. if (is_string($attributes)) {
  333. $attributes = [$attributes];
  334. }
  335. if (!is_array($attributes)) {
  336. throw new LocalizedException(__('This parameter is unknown. Verify and try again.'));
  337. }
  338. foreach ($attributes as $attrCode) {
  339. if (!isset($this->_attributesByCode[$attrCode])) {
  340. continue;
  341. }
  342. $attr = $this->getAttribute($attrCode);
  343. unset($this->_attributesByTable[$attr->getBackend()->getTable()][$attrCode]);
  344. unset($this->_attributesByCode[$attrCode]);
  345. }
  346. return $this;
  347. }
  348. /**
  349. * Get EAV config model
  350. *
  351. * @return \Magento\Eav\Model\Config
  352. */
  353. protected function _getConfig()
  354. {
  355. return $this->_eavConfig;
  356. }
  357. /**
  358. * Retrieve attribute instance by name, id or config node
  359. *
  360. * This will add the attribute configuration to entity's attributes cache
  361. *
  362. * If attribute is not found false is returned
  363. *
  364. * @param string|int|Element $attribute
  365. * @return AbstractAttribute|false
  366. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  367. */
  368. public function getAttribute($attribute)
  369. {
  370. /** @var $config \Magento\Eav\Model\Config */
  371. $config = $this->_getConfig();
  372. if (is_numeric($attribute)) {
  373. $attributeId = $attribute;
  374. $attributeInstance = $config->getAttribute($this->getEntityType(), $attributeId);
  375. if ($attributeInstance) {
  376. $attributeCode = $attributeInstance->getAttributeCode();
  377. }
  378. } elseif (is_string($attribute)) {
  379. $attributeCode = $attribute;
  380. $attributeInstance = $config->getAttribute($this->getEntityType(), $attributeCode);
  381. if (!$attributeInstance->getAttributeCode() && in_array($attribute, $this->getDefaultAttributes())) {
  382. $attributeInstance->setAttributeCode(
  383. $attribute
  384. )->setBackendType(
  385. AbstractAttribute::TYPE_STATIC
  386. )->setIsGlobal(
  387. 1
  388. )->setEntity(
  389. $this
  390. )->setEntityType(
  391. $this->getEntityType()
  392. )->setEntityTypeId(
  393. $this->getEntityType()->getId()
  394. );
  395. }
  396. } elseif ($attribute instanceof AbstractAttribute) {
  397. $attributeInstance = $attribute;
  398. $attributeCode = $attributeInstance->getAttributeCode();
  399. }
  400. if (empty($attributeInstance)
  401. || !$attributeInstance instanceof AbstractAttribute
  402. || !$attributeInstance->getId()
  403. && !in_array($attributeInstance->getAttributeCode(), $this->getDefaultAttributes())
  404. ) {
  405. return false;
  406. }
  407. $attribute = $attributeInstance;
  408. if (!$attribute->getAttributeCode()) {
  409. $attribute->setAttributeCode($attributeCode);
  410. }
  411. if (!$attribute->getAttributeModel()) {
  412. $attribute->setAttributeModel($this->_getDefaultAttributeModel());
  413. }
  414. $this->addAttribute($attribute);
  415. return $attribute;
  416. }
  417. /**
  418. * Adding attribute to entity
  419. *
  420. * @param AbstractAttribute $attribute
  421. * @param DataObject|null $object
  422. * @return $this
  423. */
  424. public function addAttribute(AbstractAttribute $attribute, $object = null)
  425. {
  426. $attribute->setEntity($this);
  427. $attributeCode = $attribute->getAttributeCode();
  428. $this->_attributesByCode[$attributeCode] = $attribute;
  429. if ($object !== null) {
  430. $suffix = $this->getAttributesCacheSuffix($object);
  431. $this->attributesByScope[$suffix][$attributeCode] = $attribute;
  432. }
  433. if ($attribute->isStatic()) {
  434. $this->_staticAttributes[$attributeCode] = $attribute;
  435. } else {
  436. $this->_attributesByTable[$attribute->getBackendTable()][$attributeCode] = $attribute;
  437. }
  438. return $this;
  439. }
  440. /**
  441. * Get attributes by scope
  442. *
  443. * @param string $suffix
  444. * @return array
  445. */
  446. private function getAttributesByScope($suffix)
  447. {
  448. return (isset($this->attributesByScope[$suffix]) && !empty($this->attributesByScope[$suffix]))
  449. ? $this->attributesByScope[$suffix]
  450. : $this->getAttributesByCode();
  451. }
  452. /**
  453. * Get attributes cache suffix.
  454. *
  455. * @param DataObject $object
  456. * @return string
  457. */
  458. private function getAttributesCacheSuffix(DataObject $object)
  459. {
  460. $attributeSetId = $object->getAttributeSetId() ?: 0;
  461. $storeId = $object->getStoreId() ?: 0;
  462. return $storeId . '-' . $attributeSetId;
  463. }
  464. /**
  465. * Retrieve partial load flag
  466. *
  467. * @param bool $flag
  468. * @return bool
  469. */
  470. public function isPartialLoad($flag = null)
  471. {
  472. $result = $this->_isPartialLoad;
  473. if ($flag !== null) {
  474. $this->_isPartialLoad = (bool)$flag;
  475. }
  476. return $result;
  477. }
  478. /**
  479. * Retrieve partial save flag
  480. *
  481. * @param bool $flag
  482. * @return bool
  483. */
  484. public function isPartialSave($flag = null)
  485. {
  486. $result = $this->_isPartialSave;
  487. if ($flag !== null) {
  488. $this->_isPartialSave = (bool) $flag;
  489. }
  490. return $result;
  491. }
  492. /**
  493. * Retrieve configuration for all attributes
  494. *
  495. * @param null|DataObject $object
  496. * @return $this
  497. */
  498. public function loadAllAttributes($object = null)
  499. {
  500. return $this->getAttributeLoader()->loadAllAttributes($this, $object);
  501. }
  502. /**
  503. * Retrieve sorted attributes
  504. *
  505. * @param int $setId
  506. * @return array
  507. */
  508. public function getSortedAttributes($setId = null)
  509. {
  510. $attributes = $this->getAttributesByCode();
  511. if ($setId === null) {
  512. $setId = $this->getEntityType()->getDefaultAttributeSetId();
  513. }
  514. // initialize set info
  515. $this->_attrSetEntity->addSetInfo($this->getEntityType(), $attributes, $setId);
  516. foreach ($attributes as $code => $attribute) {
  517. /* @var $attribute AbstractAttribute */
  518. if (!$attribute->isInSet($setId)) {
  519. unset($attributes[$code]);
  520. }
  521. }
  522. $this->_sortingSetId = $setId;
  523. uasort($attributes, [$this, 'attributesCompare']);
  524. return $attributes;
  525. }
  526. /**
  527. * Compare attributes
  528. *
  529. * @param Attribute $firstAttribute
  530. * @param Attribute $secondAttribute
  531. * @return int
  532. */
  533. public function attributesCompare($firstAttribute, $secondAttribute)
  534. {
  535. $firstSort = $firstAttribute->getSortWeight((int) $this->_sortingSetId);
  536. $secondSort = $secondAttribute->getSortWeight((int) $this->_sortingSetId);
  537. return $firstSort <=> $secondSort;
  538. }
  539. /**
  540. * Check whether the attribute is Applicable to the object
  541. *
  542. * @param DataObject $object
  543. * @param AbstractAttribute $attribute
  544. * @return bool
  545. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  546. */
  547. protected function _isApplicableAttribute($object, $attribute)
  548. {
  549. return true;
  550. }
  551. /**
  552. * Walk through the attributes and run method with optional arguments
  553. *
  554. * Returns array with results for each attribute
  555. *
  556. * if $partMethod is in format "part/method" will run method on specified part
  557. * for example: $this->walkAttributes('backend/validate');
  558. *
  559. * @param string $partMethod
  560. * @param array $args
  561. * @param null|bool $collectExceptionMessages
  562. *
  563. * @throws \Exception|\Magento\Eav\Model\Entity\Attribute\Exception
  564. * @return array
  565. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  566. * @SuppressWarnings(PHPMD.NPathComplexity)
  567. */
  568. public function walkAttributes($partMethod, array $args = [], $collectExceptionMessages = null)
  569. {
  570. $methodArr = explode('/', $partMethod);
  571. switch (sizeof($methodArr)) {
  572. case 1:
  573. $part = 'attribute';
  574. $method = $methodArr[0];
  575. break;
  576. case 2:
  577. $part = $methodArr[0];
  578. $method = $methodArr[1];
  579. break;
  580. default:
  581. break;
  582. }
  583. $results = [];
  584. $suffix = $this->getAttributesCacheSuffix($args[0]);
  585. foreach ($this->getAttributesByScope($suffix) as $attrCode => $attribute) {
  586. if (isset($args[0]) && is_object($args[0]) && !$this->_isApplicableAttribute($args[0], $attribute)) {
  587. continue;
  588. }
  589. switch ($part) {
  590. case 'attribute':
  591. $instance = $attribute;
  592. break;
  593. case 'backend':
  594. $instance = $attribute->getBackend();
  595. break;
  596. case 'frontend':
  597. $instance = $attribute->getFrontend();
  598. break;
  599. case 'source':
  600. $instance = $attribute->getSource();
  601. break;
  602. default:
  603. break;
  604. }
  605. if (!$this->_isCallableAttributeInstance($instance, $method, $args)) {
  606. continue;
  607. }
  608. try {
  609. $results[$attrCode] = call_user_func_array([$instance, $method], $args);
  610. } catch (\Magento\Eav\Model\Entity\Attribute\Exception $e) {
  611. if ($collectExceptionMessages) {
  612. $results[$attrCode] = $e->getMessage();
  613. } else {
  614. throw $e;
  615. }
  616. } catch (\Exception $e) {
  617. if ($collectExceptionMessages) {
  618. $results[$attrCode] = $e->getMessage();
  619. } else {
  620. /** @var \Magento\Eav\Model\Entity\Attribute\Exception $e */
  621. $e = $this->_universalFactory->create(
  622. \Magento\Eav\Model\Entity\Attribute\Exception::class,
  623. ['phrase' => __($e->getMessage())]
  624. );
  625. $e->setAttributeCode($attrCode)->setPart($part);
  626. throw $e;
  627. }
  628. }
  629. }
  630. return $results;
  631. }
  632. /**
  633. * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable
  634. *
  635. * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance
  636. * @param string $method
  637. * @param array $args array of arguments
  638. * @return bool
  639. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  640. */
  641. protected function _isCallableAttributeInstance($instance, $method, $args)
  642. {
  643. if (!is_object($instance) || !method_exists($instance, $method) || !is_callable([$instance, $method])) {
  644. return false;
  645. }
  646. return true;
  647. }
  648. /**
  649. * Get attributes by name array
  650. *
  651. * @return array
  652. */
  653. public function getAttributesByCode()
  654. {
  655. return $this->_attributesByCode;
  656. }
  657. /**
  658. * Get attributes by table and name array
  659. *
  660. * @return array
  661. */
  662. public function getAttributesByTable()
  663. {
  664. return $this->_attributesByTable;
  665. }
  666. /**
  667. * Get entity table name
  668. *
  669. * @return string
  670. */
  671. public function getEntityTable()
  672. {
  673. if (!$this->_entityTable) {
  674. $table = $this->getEntityType()->getEntityTable();
  675. if (!$table) {
  676. $table = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE;
  677. }
  678. $this->_entityTable = $this->_resource->getTableName($table);
  679. }
  680. return $this->_entityTable;
  681. }
  682. /**
  683. * Get link id
  684. *
  685. * @return string
  686. * @since 100.1.0
  687. */
  688. public function getLinkField()
  689. {
  690. if (!$this->linkIdField) {
  691. $indexList = $this->getConnection()->getIndexList($this->getEntityTable());
  692. $pkName = $this->getConnection()->getPrimaryKeyName($this->getEntityTable());
  693. $this->linkIdField = $indexList[$pkName]['COLUMNS_LIST'][0];
  694. if (!$this->linkIdField) {
  695. $this->linkIdField = $this->getEntityIdField();
  696. }
  697. }
  698. return $this->linkIdField;
  699. }
  700. /**
  701. * Get entity id field name in entity table
  702. *
  703. * @return string
  704. */
  705. public function getEntityIdField()
  706. {
  707. if (!$this->_entityIdField) {
  708. $this->_entityIdField = $this->getEntityType()->getEntityIdField();
  709. if (!$this->_entityIdField) {
  710. $this->_entityIdField = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_ID_FIELD;
  711. }
  712. }
  713. return $this->_entityIdField;
  714. }
  715. /**
  716. * Get default entity id field name in attribute values tables
  717. *
  718. * @return string
  719. */
  720. public function getValueEntityIdField()
  721. {
  722. return $this->getLinkField();
  723. }
  724. /**
  725. * Get prefix for value tables
  726. *
  727. * @return string
  728. */
  729. public function getValueTablePrefix()
  730. {
  731. if (!$this->_valueTablePrefix) {
  732. $prefix = (string) $this->getEntityType()->getValueTablePrefix();
  733. if (!empty($prefix)) {
  734. $this->_valueTablePrefix = $prefix;
  735. /**
  736. * entity type prefix include DB table name prefix
  737. */
  738. //$this->_resource->getTableName($prefix);
  739. } else {
  740. $this->_valueTablePrefix = $this->getEntityTable();
  741. }
  742. }
  743. return $this->_valueTablePrefix;
  744. }
  745. /**
  746. * Get entity table prefix for value
  747. *
  748. * @return string
  749. */
  750. public function getEntityTablePrefix()
  751. {
  752. if (empty($this->_entityTablePrefix)) {
  753. $prefix = $this->getEntityType()->getEntityTablePrefix();
  754. if (empty($prefix)) {
  755. $prefix = $this->getEntityType()->getEntityTable();
  756. if (empty($prefix)) {
  757. $prefix = \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE;
  758. }
  759. }
  760. $this->_entityTablePrefix = $prefix;
  761. }
  762. return $this->_entityTablePrefix;
  763. }
  764. /**
  765. * Check whether the attribute is a real field in entity table
  766. *
  767. * @see \Magento\Eav\Model\Entity\AbstractEntity::getAttribute for $attribute format
  768. * @param int|string|AbstractAttribute $attribute
  769. * @return bool
  770. */
  771. public function isAttributeStatic($attribute)
  772. {
  773. $attrInstance = $this->getAttribute($attribute);
  774. return $attrInstance && $attrInstance->getBackend()->isStatic();
  775. }
  776. /**
  777. * Validate all object's attributes against configuration
  778. *
  779. * @param DataObject $object
  780. * @throws \Magento\Eav\Model\Entity\Attribute\Exception
  781. * @return true|array
  782. */
  783. public function validate($object)
  784. {
  785. $this->loadAllAttributes($object);
  786. $result = $this->walkAttributes('backend/validate', [$object], $object->getCollectExceptionMessages());
  787. $errors = [];
  788. foreach ($result as $attributeCode => $error) {
  789. if ($error === false) {
  790. $errors[$attributeCode] = true;
  791. } elseif (is_string($error)) {
  792. $errors[$attributeCode] = $error;
  793. }
  794. }
  795. if (!$errors) {
  796. return true;
  797. }
  798. return $errors;
  799. }
  800. /**
  801. * Set new increment id to object
  802. *
  803. * @param DataObject $object
  804. * @return $this
  805. */
  806. public function setNewIncrementId(DataObject $object)
  807. {
  808. if ($object->getIncrementId()) {
  809. return $this;
  810. }
  811. $incrementId = $this->getEntityType()->fetchNewIncrementId($object->getStoreId());
  812. if ($incrementId !== false) {
  813. $object->setIncrementId($incrementId);
  814. }
  815. return $this;
  816. }
  817. /**
  818. * Check attribute unique value
  819. *
  820. * @param AbstractAttribute $attribute
  821. * @param DataObject $object
  822. * @return bool
  823. */
  824. public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object)
  825. {
  826. $connection = $this->getConnection();
  827. $select = $connection->select();
  828. $entityIdField = $this->getEntityIdField();
  829. $attributeBackend = $attribute->getBackend();
  830. if ($attributeBackend->getType() === 'static') {
  831. $value = $object->getData($attribute->getAttributeCode());
  832. $bind = ['value' => trim($value)];
  833. $select->from(
  834. $this->getEntityTable(),
  835. $entityIdField
  836. )->where(
  837. $attribute->getAttributeCode() . ' = :value'
  838. );
  839. } else {
  840. $value = $object->getData($attribute->getAttributeCode());
  841. if ($attributeBackend->getType() == 'datetime') {
  842. $value = (new \DateTime($value))->format('Y-m-d H:i:s');
  843. }
  844. $bind = [
  845. 'attribute_id' => $attribute->getId(),
  846. 'value' => trim($value),
  847. ];
  848. $entityIdField = $object->getResource()->getLinkField();
  849. $select->from(
  850. $attributeBackend->getTable(),
  851. $entityIdField
  852. )->where(
  853. 'attribute_id = :attribute_id'
  854. )->where(
  855. 'value = :value'
  856. );
  857. }
  858. if ($this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) {
  859. $bind['entity_type_id'] = $this->getTypeId();
  860. $select->where('entity_type_id = :entity_type_id');
  861. }
  862. $data = $connection->fetchCol($select, $bind);
  863. if ($object->getData($entityIdField)) {
  864. return $this->uniqueValidator->validate($attribute, $object, $this, $entityIdField, $data);
  865. }
  866. return !count($data);
  867. }
  868. /**
  869. * Retrieve default source model
  870. *
  871. * @return string
  872. */
  873. public function getDefaultAttributeSourceModel()
  874. {
  875. return \Magento\Eav\Model\Entity::DEFAULT_SOURCE_MODEL;
  876. }
  877. /**
  878. * Load entity's attributes into the object
  879. *
  880. * @param AbstractModel $object
  881. * @param int $entityId
  882. * @param array|null $attributes
  883. * @return $this
  884. */
  885. public function load($object, $entityId, $attributes = [])
  886. {
  887. \Magento\Framework\Profiler::start('EAV:load_entity');
  888. /**
  889. * Load object base row data
  890. */
  891. $object->beforeLoad($entityId);
  892. $select = $this->_getLoadRowSelect($object, $entityId);
  893. $row = $this->getConnection()->fetchRow($select);
  894. if (is_array($row)) {
  895. $object->addData($row);
  896. $this->loadAttributesForObject($attributes, $object);
  897. $this->_loadModelAttributes($object);
  898. $this->_afterLoad($object);
  899. $object->afterLoad();
  900. $object->setOrigData();
  901. $object->setHasDataChanges(false);
  902. } else {
  903. $object->isObjectNew(true);
  904. }
  905. \Magento\Framework\Profiler::stop('EAV:load_entity');
  906. return $this;
  907. }
  908. /**
  909. * Loads attributes metadata.
  910. *
  911. * @deprecated 101.0.0 Use self::loadAttributesForObject instead
  912. * @param array|null $attributes
  913. * @return $this
  914. * @since 100.1.0
  915. */
  916. protected function loadAttributesMetadata($attributes)
  917. {
  918. $this->loadAttributesForObject($attributes);
  919. }
  920. /**
  921. * Load model attributes data
  922. *
  923. * @param \Magento\Framework\Model\AbstractModel $object
  924. * @return $this
  925. */
  926. protected function _loadModelAttributes($object)
  927. {
  928. if (!$object->getId()) {
  929. return $this;
  930. }
  931. \Magento\Framework\Profiler::start('load_model_attributes');
  932. $selects = [];
  933. foreach (array_keys($this->getAttributesByTable()) as $table) {
  934. $attribute = current($this->_attributesByTable[$table]);
  935. $eavType = $attribute->getBackendType();
  936. $select = $this->_getLoadAttributesSelect($object, $table);
  937. $selects[$eavType][] = $select->columns('*');
  938. }
  939. $selectGroups = $this->_resourceHelper->getLoadAttributesSelectGroups($selects);
  940. foreach ($selectGroups as $selects) {
  941. if (!empty($selects)) {
  942. if (is_array($selects)) {
  943. $select = $this->_prepareLoadSelect($selects);
  944. } else {
  945. $select = $selects;
  946. }
  947. $values = $this->getConnection()->fetchAll($select);
  948. foreach ($values as $valueRow) {
  949. $this->_setAttributeValue($object, $valueRow);
  950. }
  951. }
  952. }
  953. \Magento\Framework\Profiler::stop('load_model_attributes');
  954. return $this;
  955. }
  956. /**
  957. * Prepare select object for loading entity attributes values
  958. *
  959. * @param array $selects
  960. * @return \Magento\Framework\DB\Select
  961. */
  962. protected function _prepareLoadSelect(array $selects)
  963. {
  964. return $this->getConnection()->select()->union($selects, \Magento\Framework\DB\Select::SQL_UNION_ALL);
  965. }
  966. /**
  967. * Retrieve select object for loading base entity row
  968. *
  969. * @param DataObject $object
  970. * @param string|int $rowId
  971. * @return \Magento\Framework\DB\Select
  972. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  973. */
  974. protected function _getLoadRowSelect($object, $rowId)
  975. {
  976. $select = $this->getConnection()->select()->from(
  977. $this->getEntityTable()
  978. )->where(
  979. $this->getEntityIdField() . ' =?',
  980. $rowId
  981. );
  982. return $select;
  983. }
  984. /**
  985. * Retrieve select object for loading entity attributes values
  986. *
  987. * @param DataObject $object
  988. * @param string $table
  989. * @return \Magento\Framework\DB\Select
  990. */
  991. protected function _getLoadAttributesSelect($object, $table)
  992. {
  993. $select = $this->getConnection()->select()->from(
  994. $table,
  995. []
  996. )->where(
  997. $this->getEntityIdField() . ' =?',
  998. $object->getId()
  999. );
  1000. return $select;
  1001. }
  1002. /**
  1003. * Initialize attribute value for object
  1004. *
  1005. * @param DataObject $object
  1006. * @param array $valueRow
  1007. * @return $this
  1008. */
  1009. protected function _setAttributeValue($object, $valueRow)
  1010. {
  1011. $attribute = $this->getAttribute($valueRow['attribute_id']);
  1012. if ($attribute) {
  1013. $attributeCode = $attribute->getAttributeCode();
  1014. $object->setData($attributeCode, $valueRow['value']);
  1015. $attribute->getBackend()->setEntityValueId($object, $valueRow['value_id']);
  1016. }
  1017. return $this;
  1018. }
  1019. /**
  1020. * Save entity's attributes into the object's resource
  1021. *
  1022. * @param \Magento\Framework\Model\AbstractModel $object
  1023. * @return $this
  1024. * @throws \Exception
  1025. * @throws AlreadyExistsException
  1026. */
  1027. public function save(\Magento\Framework\Model\AbstractModel $object)
  1028. {
  1029. /**
  1030. * Direct deleted items to delete method
  1031. */
  1032. if ($object->isDeleted()) {
  1033. return $this->delete($object);
  1034. }
  1035. if (!$object->hasDataChanges()) {
  1036. return $this;
  1037. }
  1038. $this->beginTransaction();
  1039. try {
  1040. $object->validateBeforeSave();
  1041. $object->beforeSave();
  1042. if ($object->isSaveAllowed()) {
  1043. if (!$this->isPartialSave()) {
  1044. $this->loadAllAttributes($object);
  1045. }
  1046. if ($this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE
  1047. && !$object->getEntityTypeId()
  1048. ) {
  1049. $object->setEntityTypeId($this->getTypeId());
  1050. }
  1051. $object->setParentId((int)$object->getParentId());
  1052. $this->objectRelationProcessor->validateDataIntegrity($this->getEntityTable(), $object->getData());
  1053. $this->_beforeSave($object);
  1054. $this->processSave($object);
  1055. $this->_afterSave($object);
  1056. $object->afterSave();
  1057. }
  1058. $this->addCommitCallback([$object, 'afterCommitCallback'])->commit();
  1059. $object->setHasDataChanges(false);
  1060. } catch (DuplicateException $e) {
  1061. $this->rollBack();
  1062. $object->setHasDataChanges(true);
  1063. throw new AlreadyExistsException(__('Unique constraint violation found'), $e);
  1064. } catch (\Exception $e) {
  1065. $this->rollBack();
  1066. $object->setHasDataChanges(true);
  1067. throw $e;
  1068. }
  1069. return $this;
  1070. }
  1071. /**
  1072. * Save entity process
  1073. *
  1074. * @param \Magento\Framework\Model\AbstractModel $object
  1075. * @return void
  1076. * @since 100.1.0
  1077. */
  1078. protected function processSave($object)
  1079. {
  1080. $this->_processSaveData($this->_collectSaveData($object));
  1081. }
  1082. /**
  1083. * Retrieve Object instance with original data
  1084. *
  1085. * @param DataObject $object
  1086. * @return DataObject
  1087. */
  1088. protected function _getOrigObject($object)
  1089. {
  1090. $className = get_class($object);
  1091. $origObject = $this->_universalFactory->create($className);
  1092. $origObject->setData([]);
  1093. $this->load($origObject, $object->getData($this->getEntityIdField()));
  1094. return $origObject;
  1095. }
  1096. /**
  1097. * Aggregate Data for attributes that will be deleted
  1098. *
  1099. * @param array &$delete
  1100. * @param AbstractAttribute $attribute
  1101. * @param AbstractEntity $object
  1102. * @return void
  1103. */
  1104. private function _aggregateDeleteData(&$delete, $attribute, $object)
  1105. {
  1106. foreach ($attribute->getBackend()->getAffectedFields($object) as $tableName => $valuesData) {
  1107. if (!isset($delete[$tableName])) {
  1108. $delete[$tableName] = [];
  1109. }
  1110. $delete[$tableName] = array_merge((array)$delete[$tableName], $valuesData);
  1111. }
  1112. }
  1113. /**
  1114. * Prepare entity object data for save
  1115. *
  1116. * Result array structure:
  1117. * array (
  1118. * 'newObject', 'entityRow', 'insert', 'update', 'delete'
  1119. * )
  1120. *
  1121. * @param \Magento\Framework\Model\AbstractModel $newObject
  1122. * @return array
  1123. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1124. * @SuppressWarnings(PHPMD.NPathComplexity)
  1125. */
  1126. protected function _collectSaveData($newObject)
  1127. {
  1128. $newData = $newObject->getData();
  1129. $entityId = $newObject->getData($this->getEntityIdField());
  1130. // define result data
  1131. $entityRow = [];
  1132. $insert = [];
  1133. $update = [];
  1134. $delete = [];
  1135. if (!empty($entityId)) {
  1136. $origData = $newObject->getOrigData();
  1137. /**
  1138. * get current data in db for this entity if original data is empty
  1139. */
  1140. if (empty($origData)) {
  1141. $origData = $this->_getOrigObject($newObject)->getOrigData();
  1142. }
  1143. if ($origData === null) {
  1144. $origData = [];
  1145. }
  1146. /**
  1147. * drop attributes that are unknown in new data
  1148. * not needed after introduction of partial entity loading
  1149. */
  1150. foreach ($origData as $k => $v) {
  1151. if (!array_key_exists($k, $newData)) {
  1152. unset($origData[$k]);
  1153. }
  1154. }
  1155. } else {
  1156. $origData = [];
  1157. }
  1158. $staticFields = $this->getConnection()->describeTable($this->getEntityTable());
  1159. $staticFields = array_keys($staticFields);
  1160. $attributeCodes = array_keys($this->_attributesByCode);
  1161. foreach ($newData as $k => $v) {
  1162. /**
  1163. * Check if data key is presented in static fields or attribute codes
  1164. */
  1165. if (!in_array($k, $staticFields) && !in_array($k, $attributeCodes)) {
  1166. continue;
  1167. }
  1168. $attribute = $this->getAttribute($k);
  1169. if (empty($attribute)) {
  1170. continue;
  1171. }
  1172. if (!$attribute->isInSet($newObject->getAttributeSetId()) && !in_array($k, $staticFields)) {
  1173. $this->_aggregateDeleteData($delete, $attribute, $newObject);
  1174. continue;
  1175. }
  1176. $attrId = $attribute->getAttributeId();
  1177. /**
  1178. * Only scalar values can be stored in generic tables
  1179. */
  1180. if (!$attribute->getBackend()->isScalar()) {
  1181. continue;
  1182. }
  1183. /**
  1184. * if attribute is static add to entity row and continue
  1185. */
  1186. if ($this->isAttributeStatic($k)) {
  1187. $entityRow[$k] = $this->_prepareStaticValue($k, $v);
  1188. continue;
  1189. }
  1190. /**
  1191. * Check comparability for attribute value
  1192. */
  1193. if ($this->_canUpdateAttribute($attribute, $v, $origData)) {
  1194. if ($this->_isAttributeValueEmpty($attribute, $v)) {
  1195. $this->_aggregateDeleteData($delete, $attribute, $newObject);
  1196. } elseif (!is_numeric($v) && $v !== $origData[$k] || is_numeric($v) && $v != $origData[$k]) {
  1197. $update[$attrId] = [
  1198. 'value_id' => $attribute->getBackend()->getEntityValueId($newObject),
  1199. 'value' => is_array($v) ? array_shift($v) : $v,//@TODO: MAGETWO-44182,
  1200. ];
  1201. }
  1202. } elseif (!$this->_isAttributeValueEmpty($attribute, $v)) {
  1203. $insert[$attrId] = is_array($v) ? array_shift($v) : $v;//@TODO: MAGETWO-44182
  1204. }
  1205. }
  1206. $result = compact('newObject', 'entityRow', 'insert', 'update', 'delete');
  1207. return $result;
  1208. }
  1209. /**
  1210. * Return if attribute exists in original data array.
  1211. *
  1212. * @param AbstractAttribute $attribute
  1213. * @param mixed $v New value of the attribute. Can be used in subclasses.
  1214. * @param array $origData
  1215. * @return bool
  1216. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  1217. */
  1218. protected function _canUpdateAttribute(AbstractAttribute $attribute, $v, array &$origData)
  1219. {
  1220. return array_key_exists($attribute->getAttributeCode(), $origData);
  1221. }
  1222. /**
  1223. * Retrieve static field properties
  1224. *
  1225. * @param string $field
  1226. * @return array
  1227. */
  1228. protected function _getStaticFieldProperties($field)
  1229. {
  1230. if (empty($this->_describeTable[$this->getEntityTable()])) {
  1231. $this->_describeTable[$this->getEntityTable()] = $this->getConnection()->describeTable(
  1232. $this->getEntityTable()
  1233. );
  1234. }
  1235. if (isset($this->_describeTable[$this->getEntityTable()][$field])) {
  1236. return $this->_describeTable[$this->getEntityTable()][$field];
  1237. }
  1238. return false;
  1239. }
  1240. /**
  1241. * Prepare static value for save
  1242. *
  1243. * @param string $key
  1244. * @param mixed $value
  1245. * @return mixed
  1246. */
  1247. protected function _prepareStaticValue($key, $value)
  1248. {
  1249. $fieldProp = $this->_getStaticFieldProperties($key);
  1250. if (!$fieldProp) {
  1251. return $value;
  1252. }
  1253. if ($fieldProp['DATA_TYPE'] == 'decimal') {
  1254. $value = $this->_localeFormat->getNumber($value);
  1255. }
  1256. return $value;
  1257. }
  1258. /**
  1259. * Save object collected data
  1260. *
  1261. * @param array $saveData array('newObject', 'entityRow', 'insert', 'update', 'delete')
  1262. * @return $this
  1263. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1264. * @SuppressWarnings(PHPMD.NPathComplexity)
  1265. */
  1266. protected function _processSaveData($saveData)
  1267. {
  1268. extract($saveData, EXTR_SKIP);
  1269. /**
  1270. * Import variables into the current symbol table from save data array
  1271. *
  1272. * @see \Magento\Eav\Model\Entity\AbstractEntity::_collectSaveData()
  1273. *
  1274. * @var array $entityRow
  1275. * @var \Magento\Framework\Model\AbstractModel $newObject
  1276. * @var array $insert
  1277. * @var array $update
  1278. * @var array $delete
  1279. */
  1280. $connection = $this->getConnection();
  1281. $insertEntity = true;
  1282. $entityTable = $this->getEntityTable();
  1283. $entityIdField = $this->getEntityIdField();
  1284. $entityId = $newObject->getId();
  1285. unset($entityRow[$entityIdField]);
  1286. if (!empty($entityId) && is_numeric($entityId)) {
  1287. $bind = ['entity_id' => $entityId];
  1288. $select = $connection->select()->from($entityTable, $entityIdField)->where("{$entityIdField} = :entity_id");
  1289. $result = $connection->fetchOne($select, $bind);
  1290. if ($result) {
  1291. $insertEntity = false;
  1292. }
  1293. } else {
  1294. $entityId = null;
  1295. }
  1296. /**
  1297. * Process base row
  1298. */
  1299. $entityObject = new DataObject($entityRow);
  1300. $entityRow = $this->_prepareDataForTable($entityObject, $entityTable);
  1301. if ($insertEntity) {
  1302. if (!empty($entityId)) {
  1303. $entityRow[$entityIdField] = $entityId;
  1304. $connection->insertForce($entityTable, $entityRow);
  1305. } else {
  1306. $connection->insert($entityTable, $entityRow);
  1307. $entityId = $connection->lastInsertId($entityTable);
  1308. }
  1309. $newObject->setId($entityId);
  1310. } else {
  1311. $where = sprintf('%s=%d', $connection->quoteIdentifier($entityIdField), $entityId);
  1312. $connection->update($entityTable, $entityRow, $where);
  1313. }
  1314. /**
  1315. * insert attribute values
  1316. */
  1317. if (!empty($insert)) {
  1318. foreach ($insert as $attributeId => $value) {
  1319. $attribute = $this->getAttribute($attributeId);
  1320. $this->_insertAttribute($newObject, $attribute, $value);
  1321. }
  1322. }
  1323. /**
  1324. * update attribute values
  1325. */
  1326. if (!empty($update)) {
  1327. foreach ($update as $attributeId => $v) {
  1328. $attribute = $this->getAttribute($attributeId);
  1329. $this->_updateAttribute($newObject, $attribute, $v['value_id'], $v['value']);
  1330. }
  1331. }
  1332. /**
  1333. * delete empty attribute values
  1334. */
  1335. if (!empty($delete)) {
  1336. foreach ($delete as $table => $values) {
  1337. $this->_deleteAttributes($newObject, $table, $values);
  1338. }
  1339. }
  1340. $this->_processAttributeValues();
  1341. $newObject->isObjectNew(false);
  1342. return $this;
  1343. }
  1344. /**
  1345. * Insert entity attribute value
  1346. *
  1347. * @param DataObject $object
  1348. * @param AbstractAttribute $attribute
  1349. * @param mixed $value
  1350. * @return $this
  1351. */
  1352. protected function _insertAttribute($object, $attribute, $value)
  1353. {
  1354. return $this->_saveAttribute($object, $attribute, $value);
  1355. }
  1356. /**
  1357. * Update entity attribute value
  1358. *
  1359. * @param DataObject $object
  1360. * @param AbstractAttribute $attribute
  1361. * @param mixed $valueId
  1362. * @param mixed $value
  1363. * @return $this
  1364. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  1365. */
  1366. protected function _updateAttribute($object, $attribute, $valueId, $value)
  1367. {
  1368. return $this->_saveAttribute($object, $attribute, $value);
  1369. }
  1370. /**
  1371. * Save entity attribute value
  1372. *
  1373. * Collect for mass save
  1374. *
  1375. * @param \Magento\Framework\Model\AbstractModel $object
  1376. * @param AbstractAttribute $attribute
  1377. * @param mixed $value
  1378. * @return $this
  1379. */
  1380. protected function _saveAttribute($object, $attribute, $value)
  1381. {
  1382. $table = $attribute->getBackend()->getTable();
  1383. if (!isset($this->_attributeValuesToSave[$table])) {
  1384. $this->_attributeValuesToSave[$table] = [];
  1385. }
  1386. $entityIdField = $attribute->getBackend()->getEntityIdField();
  1387. $data = [
  1388. $entityIdField => $object->getId(),
  1389. 'attribute_id' => $attribute->getId(),
  1390. 'value' => $this->_prepareValueForSave($value, $attribute),
  1391. ];
  1392. if (!$this->getEntityTable() || $this->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE) {
  1393. $data['entity_type_id'] = $object->getEntityTypeId();
  1394. }
  1395. $this->_attributeValuesToSave[$table][] = $data;
  1396. return $this;
  1397. }
  1398. /**
  1399. * Save and delete collected attribute values
  1400. *
  1401. * @return $this
  1402. */
  1403. protected function _processAttributeValues()
  1404. {
  1405. $connection = $this->getConnection();
  1406. foreach ($this->_attributeValuesToSave as $table => $data) {
  1407. $connection->insertOnDuplicate($table, $data, ['value']);
  1408. }
  1409. foreach ($this->_attributeValuesToDelete as $table => $valueIds) {
  1410. $connection->delete($table, ['value_id IN (?)' => $valueIds]);
  1411. }
  1412. // reset data arrays
  1413. $this->_attributeValuesToSave = [];
  1414. $this->_attributeValuesToDelete = [];
  1415. return $this;
  1416. }
  1417. /**
  1418. * Prepare value for save
  1419. *
  1420. * @param mixed $value
  1421. * @param AbstractAttribute $attribute
  1422. * @return mixed
  1423. */
  1424. protected function _prepareValueForSave($value, AbstractAttribute $attribute)
  1425. {
  1426. $type = $attribute->getBackendType();
  1427. if (($type == 'int' || $type == 'decimal' || $type == 'datetime') && $value === '') {
  1428. $value = null;
  1429. } elseif ($type == 'decimal') {
  1430. $value = $this->_localeFormat->getNumber($value);
  1431. }
  1432. $backendTable = $attribute->getBackendTable();
  1433. if (!isset(self::$_attributeBackendTables[$backendTable])) {
  1434. self::$_attributeBackendTables[$backendTable] = $this->getConnection()->describeTable($backendTable);
  1435. }
  1436. $describe = self::$_attributeBackendTables[$backendTable];
  1437. return $this->getConnection()->prepareColumnValue($describe['value'], $value);
  1438. }
  1439. /**
  1440. * Delete entity attribute values
  1441. *
  1442. * @param DataObject $object
  1443. * @param string $table
  1444. * @param array $info
  1445. * @return DataObject
  1446. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  1447. */
  1448. protected function _deleteAttributes($object, $table, $info)
  1449. {
  1450. $valueIds = [];
  1451. foreach ($info as $itemData) {
  1452. $valueIds[] = $itemData['value_id'];
  1453. }
  1454. if (empty($valueIds)) {
  1455. return $this;
  1456. }
  1457. if (isset($this->_attributeValuesToDelete[$table])) {
  1458. $this->_attributeValuesToDelete[$table] = array_merge($this->_attributeValuesToDelete[$table], $valueIds);
  1459. } else {
  1460. $this->_attributeValuesToDelete[$table] = $valueIds;
  1461. }
  1462. return $this;
  1463. }
  1464. /**
  1465. * Save attribute
  1466. *
  1467. * @param DataObject $object
  1468. * @param string $attributeCode
  1469. * @return $this
  1470. * @throws \Exception
  1471. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1472. */
  1473. public function saveAttribute(DataObject $object, $attributeCode)
  1474. {
  1475. $attribute = $this->getAttribute($attributeCode);
  1476. $backend = $attribute->getBackend();
  1477. $table = $backend->getTable();
  1478. $entity = $attribute->getEntity();
  1479. $connection = $this->getConnection();
  1480. $row = $this->getAttributeRow($entity, $object, $attribute);
  1481. $newValue = $object->getData($attributeCode);
  1482. if ($attribute->isValueEmpty($newValue)) {
  1483. $newValue = null;
  1484. }
  1485. $whereArr = [];
  1486. foreach ($row as $field => $value) {
  1487. $whereArr[] = $connection->quoteInto($field . '=?', $value);
  1488. }
  1489. $where = implode(' AND ', $whereArr);
  1490. $connection->beginTransaction();
  1491. try {
  1492. $select = $connection->select()->from($table, 'value_id')->where($where);
  1493. $origValueId = $connection->fetchOne($select);
  1494. if ($origValueId === false && $newValue !== null) {
  1495. $this->_insertAttribute($object, $attribute, $newValue);
  1496. } elseif ($origValueId !== false && $newValue !== null) {
  1497. $this->_updateAttribute($object, $attribute, $origValueId, $newValue);
  1498. } elseif ($origValueId !== false && $newValue === null) {
  1499. $connection->delete($table, $where);
  1500. }
  1501. $this->_processAttributeValues();
  1502. $connection->commit();
  1503. } catch (\Exception $e) {
  1504. $connection->rollBack();
  1505. throw $e;
  1506. }
  1507. return $this;
  1508. }
  1509. /**
  1510. * Return attribute row to prepare where statement
  1511. *
  1512. * @param DataObject $entity
  1513. * @param DataObject $object
  1514. * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute
  1515. * @return array
  1516. */
  1517. protected function getAttributeRow($entity, $object, $attribute)
  1518. {
  1519. $data = [
  1520. 'attribute_id' => $attribute->getId(),
  1521. $this->getLinkField() => $object->getData($this->getLinkField()),
  1522. ];
  1523. if (!$this->getEntityTable()) {
  1524. $data['entity_type_id'] = $entity->getTypeId();
  1525. }
  1526. return $data;
  1527. }
  1528. /**
  1529. * Delete entity using current object's data
  1530. *
  1531. * @param DataObject|int|string $object
  1532. * @return $this
  1533. * @throws \Exception
  1534. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  1535. */
  1536. public function delete($object)
  1537. {
  1538. try {
  1539. $connection = $this->transactionManager->start($this->getConnection());
  1540. if (is_numeric($object)) {
  1541. $id = (int) $object;
  1542. } elseif ($object instanceof \Magento\Framework\Model\AbstractModel) {
  1543. $object->beforeDelete();
  1544. $id = (int) $object->getData($this->getLinkField());
  1545. }
  1546. $this->_beforeDelete($object);
  1547. $this->evaluateDelete(
  1548. $object,
  1549. $id,
  1550. $connection
  1551. );
  1552. $this->_afterDelete($object);
  1553. if ($object instanceof \Magento\Framework\Model\AbstractModel) {
  1554. $object->isDeleted(true);
  1555. $object->afterDelete();
  1556. }
  1557. $this->transactionManager->commit();
  1558. if ($object instanceof \Magento\Framework\Model\AbstractModel) {
  1559. $object->afterDeleteCommit();
  1560. }
  1561. } catch (\Exception $e) {
  1562. $this->transactionManager->rollBack();
  1563. throw $e;
  1564. }
  1565. return $this;
  1566. }
  1567. /**
  1568. * Evaluate Delete operations
  1569. *
  1570. * @param DataObject|int|string $object
  1571. * @param string|int $id
  1572. * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
  1573. * @return void
  1574. * @throws \Exception
  1575. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  1576. * @since 100.1.0
  1577. */
  1578. protected function evaluateDelete($object, $id, $connection)
  1579. {
  1580. $where = [$this->getEntityIdField() . '=?' => $id];
  1581. $this->objectRelationProcessor->delete(
  1582. $this->transactionManager,
  1583. $connection,
  1584. $this->getEntityTable(),
  1585. $this->getConnection()->quoteInto(
  1586. $this->getEntityIdField() . '=?',
  1587. $id
  1588. ),
  1589. [$this->getEntityIdField() => $id]
  1590. );
  1591. $this->loadAllAttributes($object);
  1592. foreach ($this->getAttributesByTable() as $table => $attributes) {
  1593. $this->getConnection()->delete(
  1594. $table,
  1595. $where
  1596. );
  1597. }
  1598. }
  1599. /**
  1600. * After Load Entity process
  1601. *
  1602. * @param DataObject $object
  1603. * @return $this
  1604. */
  1605. protected function _afterLoad(DataObject $object)
  1606. {
  1607. \Magento\Framework\Profiler::start('after_load');
  1608. $this->walkAttributes('backend/afterLoad', [$object]);
  1609. \Magento\Framework\Profiler::stop('after_load');
  1610. return $this;
  1611. }
  1612. /**
  1613. * Before delete Entity process
  1614. *
  1615. * @param DataObject $object
  1616. * @return $this
  1617. */
  1618. protected function _beforeSave(DataObject $object)
  1619. {
  1620. $this->walkAttributes('backend/beforeSave', [$object]);
  1621. return $this;
  1622. }
  1623. /**
  1624. * After Save Entity process
  1625. *
  1626. * @param DataObject $object
  1627. * @return $this
  1628. */
  1629. protected function _afterSave(DataObject $object)
  1630. {
  1631. $this->walkAttributes('backend/afterSave', [$object]);
  1632. return $this;
  1633. }
  1634. /**
  1635. * Before Delete Entity process
  1636. *
  1637. * @param DataObject $object
  1638. * @return $this
  1639. */
  1640. protected function _beforeDelete(DataObject $object)
  1641. {
  1642. $this->walkAttributes('backend/beforeDelete', [$object]);
  1643. return $this;
  1644. }
  1645. /**
  1646. * After delete entity process
  1647. *
  1648. * @param DataObject $object
  1649. * @return $this
  1650. */
  1651. protected function _afterDelete(DataObject $object)
  1652. {
  1653. $this->walkAttributes('backend/afterDelete', [$object]);
  1654. return $this;
  1655. }
  1656. /**
  1657. * Retrieve Default attribute model
  1658. *
  1659. * @return string
  1660. */
  1661. protected function _getDefaultAttributeModel()
  1662. {
  1663. return \Magento\Eav\Model\Entity::DEFAULT_ATTRIBUTE_MODEL;
  1664. }
  1665. /**
  1666. * Retrieve default entity attributes
  1667. *
  1668. * @return string[]
  1669. */
  1670. protected function _getDefaultAttributes()
  1671. {
  1672. return ['entity_type_id', 'attribute_set_id', 'created_at', 'updated_at', 'parent_id', 'increment_id'];
  1673. }
  1674. /**
  1675. * Retrieve default entity static attributes
  1676. *
  1677. * @return string[]
  1678. */
  1679. public function getDefaultAttributes()
  1680. {
  1681. return array_unique(array_merge(
  1682. $this->_getDefaultAttributes(),
  1683. [$this->getEntityIdField(), $this->getLinkField()]
  1684. ));
  1685. }
  1686. /**
  1687. * Check is attribute value empty
  1688. *
  1689. * @param AbstractAttribute $attribute
  1690. * @param mixed $value
  1691. * @return bool
  1692. */
  1693. protected function _isAttributeValueEmpty(AbstractAttribute $attribute, $value)
  1694. {
  1695. return $attribute->isValueEmpty($value);
  1696. }
  1697. /**
  1698. * The getter function to get the AttributeLoaderInterface
  1699. *
  1700. * @return AttributeLoaderInterface
  1701. *
  1702. * @deprecated 100.1.0
  1703. * @since 100.1.0
  1704. */
  1705. protected function getAttributeLoader()
  1706. {
  1707. if ($this->attributeLoader === null) {
  1708. $this->attributeLoader= ObjectManager::getInstance()->get(AttributeLoaderInterface::class);
  1709. }
  1710. return $this->attributeLoader;
  1711. }
  1712. /**
  1713. * Perform actions after entity load
  1714. *
  1715. * @param DataObject $object
  1716. * @since 100.1.0
  1717. */
  1718. public function afterLoad(DataObject $object)
  1719. {
  1720. $this->_afterLoad($object);
  1721. }
  1722. /**
  1723. * Perform actions before entity save
  1724. *
  1725. * @param DataObject $object
  1726. * @since 100.1.0
  1727. */
  1728. public function beforeSave(DataObject $object)
  1729. {
  1730. $this->_beforeSave($object);
  1731. }
  1732. /**
  1733. * Perform actions after entity save
  1734. *
  1735. * @param DataObject $object
  1736. * @since 100.1.0
  1737. */
  1738. public function afterSave(DataObject $object)
  1739. {
  1740. $this->_afterSave($object);
  1741. }
  1742. /**
  1743. * Perform actions before entity delete
  1744. *
  1745. * @param DataObject $object
  1746. * @since 100.1.0
  1747. */
  1748. public function beforeDelete(DataObject $object)
  1749. {
  1750. $this->_beforeDelete($object);
  1751. }
  1752. /**
  1753. * Perform actions after entity delete
  1754. *
  1755. * @param DataObject $object
  1756. * @since 100.1.0
  1757. */
  1758. public function afterDelete(DataObject $object)
  1759. {
  1760. $this->_afterDelete($object);
  1761. }
  1762. /**
  1763. * Load attributes for object
  1764. *
  1765. * If the object will not pass all attributes for this entity type will be loaded
  1766. *
  1767. * @param array $attributes
  1768. * @param AbstractEntity|null $object
  1769. * @return void
  1770. * @since 101.0.0
  1771. */
  1772. protected function loadAttributesForObject($attributes, $object = null)
  1773. {
  1774. if (empty($attributes)) {
  1775. $this->loadAllAttributes($object);
  1776. } else {
  1777. if (!is_array($attributes)) {
  1778. $attributes = [$attributes];
  1779. }
  1780. foreach ($attributes as $attrCode) {
  1781. $this->getAttribute($attrCode);
  1782. }
  1783. }
  1784. }
  1785. }