Skip to content

Commit 31258bf

Browse files
Merge pull request #10279 from adobe-commerce-tier-4/PR_2025_12_11_muntianu
[Support Tier-4 muntianu] 12-11-2025 Regular delivery of bugfixes and improvements
2 parents 02dadc4 + f345575 commit 31258bf

File tree

47 files changed

+2821
-290
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2821
-290
lines changed

app/code/Magento/Catalog/Test/Fixture/Attribute.php

Lines changed: 91 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -7,51 +7,77 @@
77

88
namespace Magento\Catalog\Test\Fixture;
99

10+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
11+
use Magento\Catalog\Api\Data\ProductAttributeInterfaceFactory;
1012
use Magento\Catalog\Api\ProductAttributeManagementInterface;
1113
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
1214
use Magento\Catalog\Model\Product;
13-
use Magento\Catalog\Model\ResourceModel\Attribute as ResourceModelAttribute;
14-
use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute;
15-
use Magento\Eav\Model\AttributeFactory;
1615
use Magento\Eav\Setup\EavSetup;
16+
use Magento\Framework\Api\DataObjectHelper;
1717
use Magento\Framework\DataObject;
1818
use Magento\TestFramework\Fixture\Api\DataMerger;
1919
use Magento\TestFramework\Fixture\Api\ServiceFactory;
2020
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
2121
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
2222

23+
/**
24+
* Product attribute fixture
25+
*
26+
* Usage examples:
27+
*
28+
* 1. Create an attribute with default data
29+
* <pre>
30+
* #[
31+
* DataFixture(AttributeFixture::class, as: 'attribute')
32+
* ]
33+
* </pre>
34+
* 2. Create an attribute with custom data
35+
* <pre>
36+
* #[
37+
* DataFixture(AttributeFixture::class, ['is_filterable' => true], 'attribute')
38+
* ]
39+
* </pre>
40+
*/
2341
class Attribute implements RevertibleDataFixtureInterface
2442
{
2543
private const DEFAULT_DATA = [
26-
'is_wysiwyg_enabled' => false,
27-
'is_html_allowed_on_front' => true,
28-
'used_for_sort_by' => false,
29-
'is_filterable' => false,
30-
'is_filterable_in_search' => false,
31-
'is_used_in_grid' => true,
32-
'is_visible_in_grid' => true,
33-
'is_filterable_in_grid' => true,
34-
'position' => 0,
35-
'apply_to' => [],
36-
'is_searchable' => false,
37-
'is_visible_in_advanced_search' => false,
38-
'is_comparable' => false,
39-
'is_used_for_promo_rules' => false,
40-
'is_visible_on_front' => false,
41-
'used_in_product_listing' => false,
42-
'is_visible' => true,
43-
'scope' => 'store',
44-
'attribute_code' => 'product_attribute%uniqid%',
45-
'frontend_input' => 'text',
46-
'entity_type_id' => '4',
47-
'is_required' => false,
48-
'options' => [],
49-
'is_user_defined' => true,
44+
ProductAttributeInterface::ATTRIBUTE_ID => null,
45+
ProductAttributeInterface::ATTRIBUTE_CODE => 'product_attribute%uniqid%',
46+
ProductAttributeInterface::ENTITY_TYPE_ID => '4',
47+
ProductAttributeInterface::SCOPE => ProductAttributeInterface::SCOPE_GLOBAL_TEXT,
48+
ProductAttributeInterface::IS_USER_DEFINED => true,
49+
ProductAttributeInterface::IS_SEARCHABLE => false,
50+
ProductAttributeInterface::IS_FILTERABLE => false,
51+
ProductAttributeInterface::IS_FILTERABLE_IN_SEARCH => false,
52+
ProductAttributeInterface::IS_FILTERABLE_IN_GRID => true,
53+
ProductAttributeInterface::IS_VISIBLE => true,
54+
ProductAttributeInterface::IS_VISIBLE_IN_GRID => true,
55+
ProductAttributeInterface::IS_VISIBLE_IN_ADVANCED_SEARCH => false,
56+
ProductAttributeInterface::IS_VISIBLE_ON_FRONT => false,
57+
ProductAttributeInterface::IS_USED_IN_GRID => true,
58+
ProductAttributeInterface::IS_COMPARABLE => false,
59+
ProductAttributeInterface::IS_USED_FOR_PROMO_RULES => false,
60+
ProductAttributeInterface::IS_REQUIRED => false,
61+
ProductAttributeInterface::IS_UNIQUE => false,
62+
ProductAttributeInterface::IS_WYSIWYG_ENABLED => false,
63+
ProductAttributeInterface::IS_HTML_ALLOWED_ON_FRONT => true,
64+
ProductAttributeInterface::USED_IN_PRODUCT_LISTING => false,
65+
ProductAttributeInterface::USED_FOR_SORT_BY => false,
66+
ProductAttributeInterface::POSITION => 0,
67+
ProductAttributeInterface::APPLY_TO => [],
68+
ProductAttributeInterface::OPTIONS => [],
69+
ProductAttributeInterface::NOTE => null,
70+
ProductAttributeInterface::BACKEND_TYPE => 'varchar',
71+
ProductAttributeInterface::BACKEND_MODEL => null,
72+
ProductAttributeInterface::FRONTEND_INPUT => 'text',
73+
ProductAttributeInterface::FRONTEND_CLASS => null,
74+
ProductAttributeInterface::SOURCE_MODEL => null,
75+
ProductAttributeInterface::EXTENSION_ATTRIBUTES_KEY => [],
76+
ProductAttributeInterface::CUSTOM_ATTRIBUTES => [],
77+
ProductAttributeInterface::FRONTEND_LABELS => [],
5078
'default_frontend_label' => 'Product Attribute%uniqid%',
51-
'frontend_labels' => [],
52-
'backend_type' => 'varchar',
53-
'is_unique' => '0',
54-
'validation_rules' => []
79+
'validation_rules' => [],
80+
"default_value" => null,
5581
];
5682

5783
private const DEFAULT_ATTRIBUTE_SET_DATA = [
@@ -60,96 +86,60 @@ class Attribute implements RevertibleDataFixtureInterface
6086
'_sort_order' => 0,
6187
];
6288

63-
/**
64-
* @var ServiceFactory
65-
*/
66-
private $serviceFactory;
67-
68-
/**
69-
* @var ProcessorInterface
70-
*/
71-
private $dataProcessor;
72-
73-
/**
74-
* @var EavSetup
75-
*/
76-
private $eavSetup;
77-
78-
/**
79-
* @var ProductAttributeManagementInterface
80-
*/
81-
private $productAttributeManagement;
82-
83-
/**
84-
* @var AttributeFactory
85-
*/
86-
private AttributeFactory $attributeFactory;
87-
88-
/**
89-
* @var DataMerger
90-
*/
91-
private DataMerger $dataMerger;
92-
93-
/**
94-
* @var ResourceModelAttribute
95-
*/
96-
private ResourceModelAttribute $resourceModelAttribute;
97-
98-
/**
99-
* @var ProductAttributeRepositoryInterface
100-
*/
101-
private ProductAttributeRepositoryInterface $productAttributeRepository;
102-
10389
/**
10490
* @param ServiceFactory $serviceFactory
10591
* @param ProcessorInterface $dataProcessor
10692
* @param EavSetup $eavSetup
10793
* @param ProductAttributeManagementInterface $productAttributeManagement
108-
* @param AttributeFactory $attributeFactory
109-
* @param DataMerger $dataMerger
110-
* @param ResourceModelAttribute $resourceModelAttribute
94+
* @param ProductAttributeInterfaceFactory $attributeFactory
11195
* @param ProductAttributeRepositoryInterface $productAttributeRepository
96+
* @param DataObjectHelper $dataObjectHelper
97+
* @param DataMerger $dataMerger
11298
*/
11399
public function __construct(
114-
ServiceFactory $serviceFactory,
115-
ProcessorInterface $dataProcessor,
116-
EavSetup $eavSetup,
117-
ProductAttributeManagementInterface $productAttributeManagement,
118-
AttributeFactory $attributeFactory,
119-
DataMerger $dataMerger,
120-
ResourceModelAttribute $resourceModelAttribute,
121-
ProductAttributeRepositoryInterface $productAttributeRepository
100+
private readonly ServiceFactory $serviceFactory,
101+
private readonly ProcessorInterface $dataProcessor,
102+
private readonly EavSetup $eavSetup,
103+
private readonly ProductAttributeManagementInterface $productAttributeManagement,
104+
private readonly ProductAttributeInterfaceFactory $attributeFactory,
105+
private readonly ProductAttributeRepositoryInterface $productAttributeRepository,
106+
private readonly DataObjectHelper $dataObjectHelper,
107+
private readonly DataMerger $dataMerger
122108
) {
123-
$this->serviceFactory = $serviceFactory;
124-
$this->dataProcessor = $dataProcessor;
125-
$this->eavSetup = $eavSetup;
126-
$this->productAttributeManagement = $productAttributeManagement;
127-
$this->attributeFactory = $attributeFactory;
128-
$this->dataMerger = $dataMerger;
129-
$this->resourceModelAttribute = $resourceModelAttribute;
130-
$this->productAttributeRepository = $productAttributeRepository;
131109
}
132110

133111
/**
134112
* {@inheritdoc}
135113
* @param array $data Parameters. Same format as Attribute::DEFAULT_DATA.
114+
*
115+
* Additional fields:
116+
* - `_set_id`: int - attribute set ID to assign the attribute to
117+
* - `_group_id`: int - attribute group ID to assign the attribute to
118+
* - `_sort_order`: int - sort order within the attribute group
119+
*
136120
* @return DataObject|null
137121
*/
138122
public function apply(array $data = []): ?DataObject
139123
{
140-
if (array_key_exists('additional_data', $data)) {
141-
return $this->applyAttributeWithAdditionalData($data);
142-
}
143-
144-
$attribute = $this->attributeFactory->createAttribute(
145-
EavAttribute::class,
146-
$this->prepareData(array_diff_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA))
147-
);
148-
$attribute = $this->productAttributeRepository->save($attribute);
149-
124+
$attributeData = array_diff_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA);
150125
$attributeSetData = $this->prepareAttributeSetData(
151126
array_intersect_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA)
152127
);
128+
129+
$attribute = $this->attributeFactory->create();
130+
$attributeData = $this->prepareData($attributeData);
131+
132+
$this->dataObjectHelper->populateWithArray(
133+
$attribute,
134+
$attributeData,
135+
ProductAttributeInterface::class
136+
);
137+
// Add data that are not part of the interface
138+
$attribute->addData(array_diff_key($attributeData, self::DEFAULT_DATA));
139+
if (isset($attributeData['scope'])) {
140+
$attribute->setScope($attributeData['scope']);
141+
}
142+
$attribute = $this->productAttributeRepository->save($attribute);
153143

154144
$this->productAttributeManagement->assign(
155145
$attributeSetData['_set_id'],
@@ -174,26 +164,6 @@ public function revert(DataObject $data): void
174164
);
175165
}
176166

177-
/**
178-
* @param array $data Parameters. Same format as Attribute::DEFAULT_DATA.
179-
* @return DataObject|null
180-
*/
181-
private function applyAttributeWithAdditionalData(array $data = []): ?DataObject
182-
{
183-
$defaultData = array_merge(self::DEFAULT_DATA, ['additional_data' => null]);
184-
/** @var EavAttribute $attr */
185-
$attr = $this->attributeFactory->createAttribute(EavAttribute::class, $defaultData);
186-
$mergedData = $this->dataProcessor->process($this, $this->dataMerger->merge($defaultData, $data));
187-
188-
$attributeSetData = $this->prepareAttributeSetData(
189-
array_intersect_key($data, self::DEFAULT_ATTRIBUTE_SET_DATA)
190-
);
191-
192-
$attr->setData(array_merge($mergedData, $attributeSetData));
193-
$this->resourceModelAttribute->save($attr);
194-
return $attr;
195-
}
196-
197167
/**
198168
* Prepare attribute data
199169
*
@@ -202,7 +172,7 @@ private function applyAttributeWithAdditionalData(array $data = []): ?DataObject
202172
*/
203173
private function prepareData(array $data): array
204174
{
205-
$data = array_merge(self::DEFAULT_DATA, $data);
175+
$data = $this->dataMerger->merge(self::DEFAULT_DATA, $data, false);
206176
$data['frontend_label'] ??= $data['default_frontend_label'];
207177

208178
return $this->dataProcessor->process($this, $data);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Catalog\Test\Fixture;
10+
11+
use Magento\Catalog\Api\Data\ProductAttributeInterface;
12+
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
13+
use Magento\Framework\Api\SearchCriteriaBuilder;
14+
use Magento\Framework\DataObject;
15+
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
16+
17+
/**
18+
* Fixture to change price attribute scope
19+
*
20+
* Usage examples:
21+
*
22+
* 1. Change price scope to website
23+
* <pre>
24+
* #[
25+
* DataFixture(PriceScope::class, ['scope' => ProductAttributeInterface::SCOPE_WEBSITE_TEXT])
26+
* ]
27+
* </pre>
28+
*/
29+
class PriceScope implements RevertibleDataFixtureInterface
30+
{
31+
/**
32+
* @param ProductAttributeRepositoryInterface $productAttributeRepository
33+
* @param SearchCriteriaBuilder $searchCriteriaBuilder
34+
*/
35+
public function __construct(
36+
private readonly ProductAttributeRepositoryInterface $productAttributeRepository,
37+
private readonly SearchCriteriaBuilder $searchCriteriaBuilder
38+
) {
39+
}
40+
41+
/**
42+
* @inheritDoc
43+
*/
44+
public function apply(array $data = []): ?DataObject
45+
{
46+
$this->change($data['scope']);
47+
return null;
48+
}
49+
50+
/**
51+
* @inheritDoc
52+
*/
53+
public function revert(DataObject $data): void
54+
{
55+
$this->change(ProductAttributeInterface::SCOPE_GLOBAL_TEXT);
56+
}
57+
58+
/**
59+
* Change price attributes scope
60+
*
61+
* @param string $scope
62+
* @return void
63+
*/
64+
private function change(string $scope): void
65+
{
66+
$this->searchCriteriaBuilder->addFilter('frontend_input', 'price');
67+
$criteria = $this->searchCriteriaBuilder->create();
68+
foreach ($this->productAttributeRepository->getList($criteria)->getItems() as $priceAttribute) {
69+
$priceAttribute->setScope($scope);
70+
$this->productAttributeRepository->save($priceAttribute);
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)