Skip to content

Commit 897ca72

Browse files
authored
Merge branch '2.4-develop' into fixed-duplicated-ids-to-index-by-mview-changelog
2 parents 95c976d + c766d6c commit 897ca72

File tree

46 files changed

+2830
-24
lines changed

Some content is hidden

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

46 files changed

+2830
-24
lines changed

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,7 @@ public function apply(array $data = []): ?DataObject
120120
public function revert(DataObject $data): void
121121
{
122122
$service = $this->serviceFactory->create(ProductRepositoryInterface::class, 'deleteById');
123-
$service->execute(
124-
[
125-
'sku' => $data->getSku()
126-
]
127-
);
123+
$service->execute(['sku' => $data->getSku()]);
128124
}
129125

130126
/**

app/code/Magento/CatalogGraphQl/etc/graphql/di.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,20 @@
208208
</argument>
209209
</arguments>
210210
</virtualType>
211+
<type name="Magento\EavGraphQl\Model\TypeResolver\AttributeMetadata">
212+
<arguments>
213+
<argument name="entityTypes" xsi:type="array">
214+
<item name="PRODUCT" xsi:type="string">CatalogAttributeMetadata</item>
215+
</argument>
216+
</arguments>
217+
</type>
218+
<type name="Magento\Framework\GraphQl\Schema\Type\Enum\DefaultDataMapper">
219+
<arguments>
220+
<argument name="map" xsi:type="array">
221+
<item name="AttributeEntityTypeEnum" xsi:type="array">
222+
<item name="catalog_product" xsi:type="string">catalog_product</item>
223+
</item>
224+
</argument>
225+
</arguments>
226+
</type>
211227
</config>

app/code/Magento/CatalogGraphQl/etc/schema.graphqls

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,3 +531,7 @@ type SimpleWishlistItem implements WishlistItemInterface @doc(description: "Cont
531531

532532
type VirtualWishlistItem implements WishlistItemInterface @doc(description: "Contains a virtual product wish list item.") {
533533
}
534+
535+
enum AttributeEntityTypeEnum {
536+
CATALOG_PRODUCT
537+
}

app/code/Magento/Config/App/Config/Type/System.php

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66

77
namespace Magento\Config\App\Config\Type;
88

9+
use Magento\Config\App\Config\Type\System\Reader;
10+
use Magento\Framework\App\Cache\StateInterface;
11+
use Magento\Framework\App\Cache\Type\Config;
912
use Magento\Framework\App\Config\ConfigSourceInterface;
1013
use Magento\Framework\App\Config\ConfigTypeInterface;
1114
use Magento\Framework\App\Config\Spi\PostProcessorInterface;
1215
use Magento\Framework\App\Config\Spi\PreProcessorInterface;
1316
use Magento\Framework\App\ObjectManager;
14-
use Magento\Config\App\Config\Type\System\Reader;
1517
use Magento\Framework\App\ScopeInterface;
1618
use Magento\Framework\Cache\FrontendInterface;
1719
use Magento\Framework\Cache\LockGuardedCacheLoader;
20+
use Magento\Framework\Encryption\Encryptor;
1821
use Magento\Framework\Lock\LockManagerInterface;
1922
use Magento\Framework\Serialize\SerializerInterface;
2023
use Magento\Store\Model\Config\Processor\Fallback;
21-
use Magento\Framework\Encryption\Encryptor;
2224
use Magento\Store\Model\ScopeInterface as StoreScope;
23-
use Magento\Framework\App\Cache\StateInterface;
24-
use Magento\Framework\App\Cache\Type\Config;
2525

2626
/**
2727
* System configuration type
@@ -292,11 +292,13 @@ private function loadScopeData($scopeType, $scopeId)
292292
}
293293

294294
$loadAction = function () use ($scopeType, $scopeId) {
295+
/* Note: configType . '_scopes' needs to be loaded first to avoid race condition where cache finishes
296+
saving after configType . '_' . $scopeType . '_' . $scopeId but before configType . '_scopes'. */
297+
$cachedScopeData = $this->cache->load($this->configType . '_scopes');
295298
$cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
296299
$scopeData = false;
297300
if ($cachedData === false) {
298301
if ($this->availableDataScopes === null) {
299-
$cachedScopeData = $this->cache->load($this->configType . '_scopes');
300302
if ($cachedScopeData !== false) {
301303
$serializedCachedData = $this->encryptor->decrypt($cachedScopeData);
302304
$this->availableDataScopes = $this->serializer->unserialize($serializedCachedData);
@@ -437,18 +439,102 @@ private function readData(): array
437439
*/
438440
public function clean()
439441
{
440-
$this->data = [];
441442
$cleanAction = function () {
442-
$this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
443+
$this->cacheData($this->readData()); // Note: If cache is enabled, pre-load the new config data.
443444
};
444-
445+
$this->data = [];
445446
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
446-
return $cleanAction();
447+
// Note: If cache is disabled, we still clean cache in case it will be enabled later
448+
$this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
449+
return;
447450
}
451+
$this->lockQuery->lockedCleanData(self::$lockName, $cleanAction);
452+
}
448453

449-
$this->lockQuery->lockedCleanData(
450-
self::$lockName,
451-
$cleanAction
452-
);
454+
/**
455+
* Prepares data for cache by serializing and encrypting them
456+
*
457+
* Prepares data per scope to avoid reading data for all scopes on every request
458+
*
459+
* @param array $data
460+
* @return array
461+
*/
462+
private function prepareDataForCache(array $data) :array
463+
{
464+
$dataToSave = [];
465+
$dataToSave[] = [
466+
$this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($data)),
467+
$this->configType,
468+
[System::CACHE_TAG]
469+
];
470+
$dataToSave[] = [
471+
$this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($data['default'])),
472+
$this->configType . '_default',
473+
[System::CACHE_TAG]
474+
];
475+
$scopes = [];
476+
foreach ([StoreScope::SCOPE_WEBSITES, StoreScope::SCOPE_STORES] as $curScopeType) {
477+
foreach ($data[$curScopeType] ?? [] as $curScopeId => $curScopeData) {
478+
$scopes[$curScopeType][$curScopeId] = 1;
479+
$dataToSave[] = [
480+
$this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($curScopeData)),
481+
$this->configType . '_' . $curScopeType . '_' . $curScopeId,
482+
[System::CACHE_TAG]
483+
];
484+
}
485+
}
486+
$dataToSave[] = [
487+
$this->encryptor->encryptWithFastestAvailableAlgorithm($this->serializer->serialize($scopes)),
488+
$this->configType . '_scopes',
489+
[System::CACHE_TAG]
490+
];
491+
return $dataToSave;
492+
}
493+
494+
/**
495+
* Cache prepared configuration data.
496+
*
497+
* Takes data prepared by prepareDataForCache
498+
*
499+
* @param array $dataToSave
500+
* @return void
501+
*/
502+
private function cachePreparedData(array $dataToSave) : void
503+
{
504+
foreach ($dataToSave as $datumToSave) {
505+
$this->cache->save($datumToSave[0], $datumToSave[1], $datumToSave[2]);
506+
}
507+
}
508+
509+
/**
510+
* Gets configuration then cleans and warms it while locked
511+
*
512+
* This is to reduce the lock time after flushing config cache.
513+
*
514+
* @param callable $cleaner
515+
* @return void
516+
*/
517+
public function cleanAndWarmDefaultScopeData(callable $cleaner)
518+
{
519+
if (!$this->cacheState->isEnabled(Config::TYPE_IDENTIFIER)) {
520+
$cleaner();
521+
return;
522+
}
523+
$loadAction = function () {
524+
return false;
525+
};
526+
$dataCollector = function () use ($cleaner) {
527+
/* Note: call to readData() needs to be inside lock to avoid race conditions such as multiple
528+
saves at the same time. */
529+
$newData = $this->readData();
530+
$preparedData = $this->prepareDataForCache($newData);
531+
unset($newData);
532+
$cleaner(); // Note: This is where other readers start waiting for us to finish saving cache.
533+
return $preparedData;
534+
};
535+
$dataSaver = function (array $preparedData) {
536+
$this->cachePreparedData($preparedData);
537+
};
538+
$this->lockQuery->lockedLoadData(self::$lockName, $loadAction, $dataCollector, $dataSaver);
453539
}
454540
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Config\Plugin\Framework\App\Cache\TypeList;
9+
10+
use Magento\Config\App\Config\Type\System;
11+
use Magento\Framework\App\Cache\Type\Config as TypeConfig;
12+
use Magento\Framework\App\Cache\TypeList;
13+
14+
/**
15+
* Plugin that for warms config cache when config cache is cleaned.
16+
* This is to reduce the lock time after flushing config cache.
17+
*/
18+
class WarmConfigCache
19+
{
20+
/**
21+
* @var System
22+
*/
23+
private $system;
24+
25+
/**
26+
* @param System $system
27+
*/
28+
public function __construct(System $system)
29+
{
30+
$this->system = $system;
31+
}
32+
33+
/**
34+
* Around plugin for cache's clean type method
35+
*
36+
* @param TypeList $subject
37+
* @param callable $proceed
38+
* @param string $typeCode
39+
* @return void
40+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
41+
*/
42+
public function aroundCleanType(TypeList $subject, callable $proceed, $typeCode)
43+
{
44+
if (TypeConfig::TYPE_IDENTIFIER !== $typeCode) {
45+
return $proceed($typeCode);
46+
}
47+
$cleaner = function () use ($proceed, $typeCode) {
48+
return $proceed($typeCode);
49+
};
50+
$this->system->cleanAndWarmDefaultScopeData($cleaner);
51+
}
52+
}

app/code/Magento/Config/etc/di.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,7 @@
380380
<argument name="configStructure" xsi:type="object">\Magento\Config\Model\Config\Structure\Proxy</argument>
381381
</arguments>
382382
</type>
383+
<type name="Magento\Framework\App\Cache\TypeList">
384+
<plugin name="warm_config_cache" type="Magento\Config\Plugin\Framework\App\Cache\TypeList\WarmConfigCache"/>
385+
</type>
383386
</config>
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Customer\Test\Fixture;
9+
10+
use Magento\Customer\Model\Attribute;
11+
use Magento\Customer\Model\ResourceModel\Attribute as ResourceModelAttribute;
12+
use Magento\Eav\Api\AttributeRepositoryInterface;
13+
use Magento\Eav\Model\AttributeFactory;
14+
use Magento\Framework\DataObject;
15+
use Magento\Framework\Exception\InvalidArgumentException;
16+
use Magento\TestFramework\Fixture\Api\DataMerger;
17+
use Magento\TestFramework\Fixture\Data\ProcessorInterface;
18+
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
19+
20+
class CustomerAttribute implements RevertibleDataFixtureInterface
21+
{
22+
private const DEFAULT_DATA = [
23+
'entity_type_id' => null,
24+
'attribute_id' => null,
25+
'attribute_code' => 'attribute%uniqid%',
26+
'default_frontend_label' => 'Attribute%uniqid%',
27+
'frontend_labels' => [],
28+
'frontend_input' => 'text',
29+
'backend_type' => 'varchar',
30+
'is_required' => false,
31+
'is_user_defined' => true,
32+
'note' => null,
33+
'backend_model' => null,
34+
'source_model' => null,
35+
'default_value' => null,
36+
'is_unique' => '0',
37+
'frontend_class' => null,
38+
'used_in_forms' => [],
39+
];
40+
41+
/**
42+
* @var DataMerger
43+
*/
44+
private DataMerger $dataMerger;
45+
46+
/**
47+
* @var ProcessorInterface
48+
*/
49+
private ProcessorInterface $processor;
50+
51+
/**
52+
* @var AttributeFactory
53+
*/
54+
private AttributeFactory $attributeFactory;
55+
56+
/**
57+
* @var ResourceModelAttribute
58+
*/
59+
private ResourceModelAttribute $resourceModelAttribute;
60+
61+
/**
62+
* @var AttributeRepositoryInterface
63+
*/
64+
private AttributeRepositoryInterface $attributeRepository;
65+
66+
/**
67+
* @param DataMerger $dataMerger
68+
* @param ProcessorInterface $processor
69+
* @param AttributeRepositoryInterface $attributeRepository
70+
* @param AttributeFactory $attributeFactory
71+
* @param ResourceModelAttribute $resourceModelAttribute
72+
*/
73+
public function __construct(
74+
DataMerger $dataMerger,
75+
ProcessorInterface $processor,
76+
AttributeRepositoryInterface $attributeRepository,
77+
AttributeFactory $attributeFactory,
78+
ResourceModelAttribute $resourceModelAttribute
79+
) {
80+
$this->dataMerger = $dataMerger;
81+
$this->processor = $processor;
82+
$this->attributeFactory = $attributeFactory;
83+
$this->resourceModelAttribute = $resourceModelAttribute;
84+
$this->attributeRepository = $attributeRepository;
85+
}
86+
87+
/**
88+
* @inheritdoc
89+
*/
90+
public function apply(array $data = []): ?DataObject
91+
{
92+
if (empty($data['entity_type_id'])) {
93+
throw new InvalidArgumentException(
94+
__(
95+
'"%field" value is required to create an attribute',
96+
[
97+
'field' => 'entity_type_id'
98+
]
99+
)
100+
);
101+
}
102+
103+
/** @var Attribute $attr */
104+
$attr = $this->attributeFactory->createAttribute(Attribute::class, self::DEFAULT_DATA);
105+
$mergedData = $this->processor->process($this, $this->dataMerger->merge(self::DEFAULT_DATA, $data));
106+
$attr->setData($mergedData);
107+
$this->resourceModelAttribute->save($attr);
108+
return $attr;
109+
}
110+
111+
/**
112+
* @inheritdoc
113+
*/
114+
public function revert(DataObject $data): void
115+
{
116+
$this->attributeRepository->deleteById($data['attribute_id']);
117+
}
118+
}

0 commit comments

Comments
 (0)