AbstractExtensibleModel.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Model;
  7. use Magento\Framework\Api\AttributeValueFactory;
  8. use Magento\Framework\Api\ExtensionAttributesFactory;
  9. /**
  10. * Abstract model with custom attributes support.
  11. *
  12. * This class defines basic data structure of how custom attributes are stored in an ExtensibleModel.
  13. * Implementations may choose to process custom attributes as their persistence requires them to.
  14. * @SuppressWarnings(PHPMD.NumberOfChildren)
  15. */
  16. abstract class AbstractExtensibleModel extends AbstractModel implements
  17. \Magento\Framework\Api\CustomAttributesDataInterface
  18. {
  19. /**
  20. * @var ExtensionAttributesFactory
  21. */
  22. protected $extensionAttributesFactory;
  23. /**
  24. * @var \Magento\Framework\Api\ExtensionAttributesInterface
  25. */
  26. protected $extensionAttributes;
  27. /**
  28. * @var AttributeValueFactory
  29. */
  30. protected $customAttributeFactory;
  31. /**
  32. * @var string[]
  33. */
  34. protected $customAttributesCodes = null;
  35. /**
  36. * @var bool
  37. */
  38. protected $customAttributesChanged = false;
  39. /**
  40. * @param \Magento\Framework\Model\Context $context
  41. * @param \Magento\Framework\Registry $registry
  42. * @param ExtensionAttributesFactory $extensionFactory
  43. * @param AttributeValueFactory $customAttributeFactory
  44. * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
  45. * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
  46. * @param array $data
  47. */
  48. public function __construct(
  49. \Magento\Framework\Model\Context $context,
  50. \Magento\Framework\Registry $registry,
  51. ExtensionAttributesFactory $extensionFactory,
  52. AttributeValueFactory $customAttributeFactory,
  53. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  54. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  55. array $data = []
  56. ) {
  57. $this->extensionAttributesFactory = $extensionFactory;
  58. $this->customAttributeFactory = $customAttributeFactory;
  59. $data = $this->filterCustomAttributes($data);
  60. parent::__construct($context, $registry, $resource, $resourceCollection, $data);
  61. if (isset($data['id'])) {
  62. $this->setId($data['id']);
  63. }
  64. if (isset($data[self::EXTENSION_ATTRIBUTES_KEY]) && is_array($data[self::EXTENSION_ATTRIBUTES_KEY])) {
  65. $this->populateExtensionAttributes($data[self::EXTENSION_ATTRIBUTES_KEY]);
  66. }
  67. }
  68. /**
  69. * Verify custom attributes set on $data and unset if not a valid custom attribute
  70. *
  71. * @param array $data
  72. * @return array processed data
  73. */
  74. protected function filterCustomAttributes($data)
  75. {
  76. if (empty($data[self::CUSTOM_ATTRIBUTES])) {
  77. return $data;
  78. }
  79. $customAttributesCodes = $this->getCustomAttributesCodes();
  80. $data[self::CUSTOM_ATTRIBUTES] = array_intersect_key(
  81. (array)$data[self::CUSTOM_ATTRIBUTES],
  82. array_flip($customAttributesCodes)
  83. );
  84. foreach ($data[self::CUSTOM_ATTRIBUTES] as $code => $value) {
  85. if (!($value instanceof \Magento\Framework\Api\AttributeInterface)) {
  86. $data[self::CUSTOM_ATTRIBUTES][$code] = $this->customAttributeFactory->create()
  87. ->setAttributeCode($code)
  88. ->setValue($value);
  89. }
  90. }
  91. return $data;
  92. }
  93. /**
  94. * Initialize customAttributes based on existing data
  95. *
  96. * @return $this
  97. */
  98. protected function initializeCustomAttributes()
  99. {
  100. if (!isset($this->_data[self::CUSTOM_ATTRIBUTES]) || $this->customAttributesChanged) {
  101. if (!empty($this->_data[self::CUSTOM_ATTRIBUTES])) {
  102. $customAttributes = $this->_data[self::CUSTOM_ATTRIBUTES];
  103. } else {
  104. $customAttributes = [];
  105. }
  106. $customAttributeCodes = $this->getCustomAttributesCodes();
  107. foreach ($customAttributeCodes as $customAttributeCode) {
  108. if (isset($this->_data[self::CUSTOM_ATTRIBUTES][$customAttributeCode])) {
  109. $customAttribute = $this->customAttributeFactory->create()
  110. ->setAttributeCode($customAttributeCode)
  111. ->setValue($this->_data[self::CUSTOM_ATTRIBUTES][$customAttributeCode]->getValue());
  112. $customAttributes[$customAttributeCode] = $customAttribute;
  113. } elseif (isset($this->_data[$customAttributeCode])) {
  114. $customAttribute = $this->customAttributeFactory->create()
  115. ->setAttributeCode($customAttributeCode)
  116. ->setValue($this->_data[$customAttributeCode]);
  117. $customAttributes[$customAttributeCode] = $customAttribute;
  118. }
  119. }
  120. $this->_data[self::CUSTOM_ATTRIBUTES] = $customAttributes;
  121. $this->customAttributesChanged = false;
  122. }
  123. }
  124. /**
  125. * Retrieve custom attributes values.
  126. *
  127. * @return \Magento\Framework\Api\AttributeInterface[]|null
  128. */
  129. public function getCustomAttributes()
  130. {
  131. $this->initializeCustomAttributes();
  132. // Returning as a sequential array (instead of stored associative array) to be compatible with the interface
  133. return array_values($this->_data[self::CUSTOM_ATTRIBUTES]);
  134. }
  135. /**
  136. * Get an attribute value.
  137. *
  138. * @param string $attributeCode
  139. * @return \Magento\Framework\Api\AttributeInterface|null null if the attribute has not been set
  140. */
  141. public function getCustomAttribute($attributeCode)
  142. {
  143. $this->initializeCustomAttributes();
  144. return $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] ?? null;
  145. }
  146. /**
  147. * {@inheritdoc}
  148. */
  149. public function setCustomAttributes(array $attributes)
  150. {
  151. return $this->setData(self::CUSTOM_ATTRIBUTES, $attributes);
  152. }
  153. /**
  154. * {@inheritdoc}
  155. */
  156. public function setCustomAttribute($attributeCode, $attributeValue)
  157. {
  158. $customAttributesCodes = $this->getCustomAttributesCodes();
  159. /* If key corresponds to custom attribute code, populate custom attributes */
  160. if (in_array($attributeCode, $customAttributesCodes)) {
  161. $attribute = $this->customAttributeFactory->create();
  162. $attribute->setAttributeCode($attributeCode)
  163. ->setValue($attributeValue);
  164. $this->_data[self::CUSTOM_ATTRIBUTES][$attributeCode] = $attribute;
  165. }
  166. return $this;
  167. }
  168. /**
  169. * {@inheritdoc}
  170. *
  171. * Added custom attributes support.
  172. */
  173. public function setData($key, $value = null)
  174. {
  175. if (is_array($key)) {
  176. $key = $this->filterCustomAttributes($key);
  177. } elseif ($key == self::CUSTOM_ATTRIBUTES) {
  178. $filteredData = $this->filterCustomAttributes([self::CUSTOM_ATTRIBUTES => $value]);
  179. $value = $filteredData[self::CUSTOM_ATTRIBUTES];
  180. }
  181. $this->customAttributesChanged = true;
  182. parent::setData($key, $value);
  183. return $this;
  184. }
  185. /**
  186. * {@inheritdoc}
  187. *
  188. * Unset customAttributesChanged flag
  189. */
  190. public function unsetData($key = null)
  191. {
  192. if (is_string($key) && isset($this->_data[self::CUSTOM_ATTRIBUTES][$key])) {
  193. unset($this->_data[self::CUSTOM_ATTRIBUTES][$key]);
  194. }
  195. return parent::unsetData($key);
  196. }
  197. /**
  198. * Convert custom values if necessary
  199. *
  200. * @param array $customAttributes
  201. * @return void
  202. */
  203. protected function convertCustomAttributeValues(array &$customAttributes)
  204. {
  205. foreach ($customAttributes as $attributeCode => $attributeValue) {
  206. if ($attributeValue instanceof \Magento\Framework\Api\AttributeValue) {
  207. $customAttributes[$attributeCode] = $attributeValue->getValue();
  208. }
  209. }
  210. }
  211. /**
  212. * Object data getter
  213. *
  214. * If $key is not defined will return all the data as an array.
  215. * Otherwise it will return value of the element specified by $key.
  216. * It is possible to use keys like a/b/c for access nested array data
  217. *
  218. * If $index is specified it will assume that attribute data is an array
  219. * and retrieve corresponding member. If data is the string - it will be explode
  220. * by new line character and converted to array.
  221. *
  222. * In addition to parent implementation custom attributes support is added.
  223. *
  224. * @param string $key
  225. * @param string|int $index
  226. * @return mixed
  227. */
  228. public function getData($key = '', $index = null)
  229. {
  230. if ($key === self::CUSTOM_ATTRIBUTES) {
  231. throw new \LogicException("Custom attributes array should be retrieved via getCustomAttributes() only.");
  232. } elseif ($key === '') {
  233. /** Represent model data and custom attributes as a flat array */
  234. $customAttributes = isset($this->_data[self::CUSTOM_ATTRIBUTES])
  235. ? $this->_data[self::CUSTOM_ATTRIBUTES]
  236. : [];
  237. $this->convertCustomAttributeValues($customAttributes);
  238. $data = array_merge($this->_data, $customAttributes);
  239. unset($data[self::CUSTOM_ATTRIBUTES]);
  240. } else {
  241. $data = parent::getData($key, $index);
  242. if ($data === null) {
  243. /** Try to find necessary data in custom attributes */
  244. $data = isset($this->_data[self::CUSTOM_ATTRIBUTES][$key])
  245. ? $this->_data[self::CUSTOM_ATTRIBUTES][$key]
  246. : null;
  247. if ($data instanceof \Magento\Framework\Api\AttributeValue) {
  248. $data = $data->getValue();
  249. }
  250. if (null !== $index && isset($data[$index])) {
  251. return $data[$index];
  252. }
  253. }
  254. }
  255. return $data;
  256. }
  257. /**
  258. * Get a list of custom attribute codes.
  259. *
  260. * By default, entity can be extended only using extension attributes functionality.
  261. *
  262. * @return string[]
  263. */
  264. protected function getCustomAttributesCodes()
  265. {
  266. return [];
  267. }
  268. /**
  269. * Receive a list of EAV attributes using provided metadata service.
  270. *
  271. * Can be used in child classes, which represent EAV entities.
  272. *
  273. * @param \Magento\Framework\Api\MetadataServiceInterface $metadataService
  274. * @return string[]
  275. */
  276. protected function getEavAttributesCodes(\Magento\Framework\Api\MetadataServiceInterface $metadataService)
  277. {
  278. $attributeCodes = [];
  279. $customAttributesMetadata = $metadataService->getCustomAttributesMetadata(get_class($this));
  280. if (is_array($customAttributesMetadata)) {
  281. /** @var $attribute \Magento\Framework\Api\MetadataObjectInterface */
  282. foreach ($customAttributesMetadata as $attribute) {
  283. $attributeCodes[] = $attribute->getAttributeCode();
  284. }
  285. }
  286. return $attributeCodes;
  287. }
  288. /**
  289. * Identifier setter
  290. *
  291. * @param mixed $value
  292. * @return $this
  293. */
  294. public function setId($value)
  295. {
  296. parent::setId($value);
  297. return $this->setData('id', $value);
  298. }
  299. /**
  300. * Set an extension attributes object.
  301. *
  302. * @param \Magento\Framework\Api\ExtensionAttributesInterface $extensionAttributes
  303. * @return $this
  304. */
  305. protected function _setExtensionAttributes(\Magento\Framework\Api\ExtensionAttributesInterface $extensionAttributes)
  306. {
  307. $this->_data[self::EXTENSION_ATTRIBUTES_KEY] = $extensionAttributes;
  308. return $this;
  309. }
  310. /**
  311. * Retrieve existing extension attributes object or create a new one.
  312. *
  313. * @return \Magento\Framework\Api\ExtensionAttributesInterface
  314. */
  315. protected function _getExtensionAttributes()
  316. {
  317. if (!$this->getData(self::EXTENSION_ATTRIBUTES_KEY)) {
  318. $this->populateExtensionAttributes([]);
  319. }
  320. return $this->getData(self::EXTENSION_ATTRIBUTES_KEY);
  321. }
  322. /**
  323. * Instantiate extension attributes object and populate it with the provided data.
  324. *
  325. * @param array $extensionAttributesData
  326. * @return void
  327. */
  328. private function populateExtensionAttributes(array $extensionAttributesData = [])
  329. {
  330. $extensionAttributes = $this->extensionAttributesFactory->create(get_class($this), $extensionAttributesData);
  331. $this->_setExtensionAttributes($extensionAttributes);
  332. }
  333. /**
  334. * @inheritdoc
  335. */
  336. public function __sleep()
  337. {
  338. return array_diff(parent::__sleep(), ['extensionAttributesFactory', 'customAttributeFactory']);
  339. }
  340. /**
  341. * @inheritdoc
  342. */
  343. public function __wakeup()
  344. {
  345. parent::__wakeup();
  346. $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
  347. $this->extensionAttributesFactory = $objectManager->get(ExtensionAttributesFactory::class);
  348. $this->customAttributeFactory = $objectManager->get(AttributeValueFactory::class);
  349. }
  350. }