Skip to content

Commit 9b8d1f9

Browse files
Merge branch '2.4-develop' into ergohack-patch-4
2 parents a9adff4 + 1b99b6d commit 9b8d1f9

File tree

5 files changed

+168
-52
lines changed

5 files changed

+168
-52
lines changed

app/code/Magento/GraphQl/Model/Query/Resolver/Context.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
* @deprecated 100.3.3
1717
* @see \Magento\GraphQl\Model\Query\Context
1818
*/
19-
class Context extends \Magento\Framework\Model\AbstractExtensibleModel implements ContextInterface
19+
class Context extends \Magento\Framework\Model\AbstractExtensibleModel implements
20+
ContextInterface,
21+
ResetAfterRequestInterface
2022
{
2123
/**#@+
2224
* Constants defined for type of context

lib/internal/Magento/Framework/ObjectManager/Resetter/Resetter.php

Lines changed: 155 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
namespace Magento\Framework\ObjectManager\Resetter;
99

10-
use Magento\Framework\App\ObjectManager;
10+
use Magento\Framework\Component\ComponentRegistrar;
11+
use Magento\Framework\Component\ComponentRegistrarInterface;
12+
use Magento\Framework\Exception\LocalizedException;
1113
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
1214
use Magento\Framework\ObjectManagerInterface;
1315
use WeakMap;
@@ -17,7 +19,8 @@
1719
*/
1820
class Resetter implements ResetterInterface
1921
{
20-
public const RESET_PATH = '/app/etc/reset.php';
22+
public const RESET_PATH = 'reset.json';
23+
private const RESET_STATE_METHOD = '_resetState';
2124

2225
/** @var WeakMap instances to be reset after request */
2326
private WeakMap $resetAfterWeakMap;
@@ -28,40 +31,56 @@ class Resetter implements ResetterInterface
2831
/** @var WeakMapSorter|null Note: We use temporal coupling here because of chicken/egg during bootstrapping */
2932
private ?WeakMapSorter $weakMapSorter = null;
3033

31-
/**
32-
* @var array
33-
*
34-
*/
35-
private array $classList = [
36-
//phpcs:disable Magento2.PHP.LiteralNamespaces
37-
'Magento\Framework\GraphQl\Query\Fields' => true,
38-
'Magento\Store\Model\Store' => [
39-
"_baseUrlCache" => [],
40-
"_configCache" => null,
41-
"_configCacheBaseNodes" => [],
42-
"_dirCache" => [],
43-
"_urlCache" => []
44-
]
45-
];
34+
/** @var array */
35+
private array $reflectionCache = [];
36+
37+
/** @var array */
38+
private array $isObjectInClassListCache = [];
39+
40+
/** @var array */
41+
private readonly array $classList;
42+
43+
/** @var array */
44+
private array $sortedClassListsByClass = [];
4645

4746
/**
48-
* @var array
47+
* @param ComponentRegistrarInterface|null $componentRegistrar
48+
* @param array $classList
49+
* @return void
50+
* @phpcs:disable Magento2.Functions.DiscouragedFunction
4951
*/
50-
private array $reflectionCache = [];
52+
public function __construct(
53+
private ?ComponentRegistrarInterface $componentRegistrar = null,
54+
array $classList = [],
55+
) {
56+
if (null === $this->componentRegistrar) {
57+
$this->componentRegistrar = new ComponentRegistrar();
58+
}
59+
foreach ($this->getPaths() as $resetPath) {
60+
if (!\file_exists($resetPath)) {
61+
continue;
62+
}
63+
$resetData = \json_decode(\file_get_contents($resetPath), true);
64+
if (!$resetData) {
65+
throw new LocalizedException(__('Error parsing %1', $resetPath));
66+
}
67+
$classList += $resetData;
68+
}
69+
$this->classList = $classList;
70+
$this->resetAfterWeakMap = new WeakMap;
71+
}
5172

5273
/**
53-
* Constructor
74+
* Get paths for reset json
5475
*
55-
* @return void
56-
* @phpcs:disable Magento2.Functions.DiscouragedFunction
76+
* @return \Generator<string>
5777
*/
58-
public function __construct()
78+
private function getPaths(): \Generator
5979
{
60-
if (\file_exists(BP . self::RESET_PATH)) {
61-
// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile
62-
$this->classList = array_replace($this->classList, (require BP . self::RESET_PATH));
80+
yield BP . '/app/etc/' . self::RESET_PATH;
81+
foreach ($this->componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $modulePath) {
82+
yield $modulePath . '/etc/' . self::RESET_PATH;
6383
}
64-
$this->resetAfterWeakMap = new WeakMap;
6584
}
6685

6786
/**
@@ -72,7 +91,10 @@ public function __construct()
7291
*/
7392
public function addInstance(object $instance) : void
7493
{
75-
if ($instance instanceof ResetAfterRequestInterface || isset($this->classList[\get_class($instance)])) {
94+
if ($instance instanceof ResetAfterRequestInterface
95+
|| \method_exists($instance, self::RESET_STATE_METHOD)
96+
|| $this->isObjectInClassList($instance)
97+
) {
7698
$this->resetAfterWeakMap[$instance] = true;
7799
}
78100
}
@@ -116,37 +138,124 @@ public function setObjectManager(ObjectManagerInterface $objectManager) : void
116138
}
117139

118140
/**
119-
* State reset without reflection
141+
* Checks if the object is in the class list uses inheritance (via instanceof)
142+
*
143+
* @param object $object
144+
* @return bool
145+
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
146+
*/
147+
public function isObjectInClassList(object $object)
148+
{
149+
$className = \get_class($object);
150+
$isObjectInClassListCachedValue = $this->isObjectInClassListCache[$className] ?? null;
151+
if (null !== $isObjectInClassListCachedValue) {
152+
return $isObjectInClassListCachedValue;
153+
}
154+
foreach ($this->classList as $key => $value) {
155+
if ($object instanceof $key) {
156+
$this->isObjectInClassListCache[$className] = true;
157+
return true;
158+
}
159+
}
160+
$this->isObjectInClassListCache[$className] = false;
161+
return false;
162+
}
163+
164+
/**
165+
* State reset using reflection (or RESET_STATE_METHOD instead if it exists)
120166
*
121167
* @param object $instance
122168
* @return void
123169
* @throws \ReflectionException
170+
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
124171
*/
125172
private function resetStateWithReflection(object $instance)
126173
{
127-
$className = \get_class($instance);
174+
if (\method_exists($instance, self::RESET_STATE_METHOD)) {
175+
$instance->{self::RESET_STATE_METHOD}();
176+
return;
177+
}
178+
$className = get_class($instance);
179+
if (!array_key_exists($className, $this->sortedClassListsByClass)) {
180+
$temporaryClassList = [];
181+
foreach ($this->classList as $key => $value) {
182+
if ($instance instanceof $key) {
183+
$temporaryClassList[] = $key;
184+
}
185+
}
186+
$this->sortClasses($temporaryClassList);
187+
$this->sortedClassListsByClass[$className] = $temporaryClassList;
188+
}
189+
foreach ($this->sortedClassListsByClass[$className] as $currentClassName) {
190+
$this->resetStateWithReflectionByClassName($instance, $currentClassName);
191+
}
192+
}
193+
194+
/**
195+
* Sorts an array of strings that are class names and interface names
196+
*
197+
* Note: This sorting algorithm only takes arrays that are keyed by contiguous numbers starting at zero.
198+
* Note: This sorting algorithm works with comparators that return false on unrelated items.
199+
*
200+
* @param array $array
201+
* @return void
202+
*/
203+
private function sortClasses(array &$array) : void
204+
{
205+
$i = 0;
206+
$count = count($array);
207+
while ($i + 1 < $count) {
208+
for ($j = $i + 1; $j < $count; $j++) {
209+
if ($this->sortClassesComparitor($array[$i], $array[$j])) {
210+
$swapTemp = $array[$i];
211+
$array[$i] = $array[$j];
212+
$array[$j] = $swapTemp;
213+
continue 2;
214+
}
215+
}
216+
$i++;
217+
}
218+
}
219+
220+
/**
221+
* Comparator for class/interface sorter that returns true if $b should come before $a.
222+
*
223+
* @param string $a
224+
* @param string $b
225+
* @return bool
226+
*/
227+
private function sortClassesComparitor(string $a, string $b) : bool
228+
{
229+
if (is_a($a, $b, true)) {
230+
return true;
231+
}
232+
if (is_a($b, $a, true)) {
233+
return false;
234+
}
235+
if (interface_exists($a) && class_exists($b)) {
236+
return true; // Note: If they aren't related, classes should come before interfaces
237+
}
238+
return false; // No relation
239+
}
240+
241+
/**
242+
* State reset using reflection using specific className
243+
*
244+
* @param object $instance
245+
* @param string $className
246+
* @return void
247+
*/
248+
private function resetStateWithReflectionByClassName(object $instance, string $className)
249+
{
250+
$classResetValues = $this->classList[$className] ?? [];
128251
$reflectionClass = $this->reflectionCache[$className]
129252
?? $this->reflectionCache[$className] = new \ReflectionClass($className);
130253
foreach ($reflectionClass->getProperties() as $property) {
131-
$type = $property->getType()?->getName();
132-
if (empty($type) && preg_match('/@var\s+([^\s]+)/', $property->getDocComment(), $matches)) {
133-
$type = $matches[1];
134-
if (\str_contains($type, '[]')) {
135-
$type = 'array';
136-
}
137-
}
138254
$name = $property->getName();
139-
if (!in_array($type, ['bool', 'array', 'null', 'true', 'false'], true)) {
255+
if (!array_key_exists($name, $classResetValues)) {
140256
continue;
141257
}
142-
$value = $this->classList[$className][$name] ??
143-
match ($type) {
144-
'bool' => false,
145-
'true' => true,
146-
'false' => false,
147-
'array' => [],
148-
'null' => null,
149-
};
258+
$value = $classResetValues[$name];
150259
$property->setAccessible(true);
151260
$property->setValue($instance, $value);
152261
$property->setAccessible(false);

lib/internal/Magento/Framework/ObjectManager/Resetter/ResetterFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ResetterFactory
1818
private static string $resetterClassName = Resetter::class;
1919

2020
/**
21-
* Create resseter factory
21+
* Create resseter instance
2222
*
2323
* @return ResetterInterface
2424
* @phpcs:disable Magento2.Functions.StaticFunction

lib/internal/Magento/Framework/TestFramework/ApplicationStateComparator/Resetter.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Magento\Framework\TestFramework\ApplicationStateComparator;
99

10+
use Magento\Framework\Component\ComponentRegistrarInterface;
1011
use Magento\Framework\ObjectManager\Resetter\Resetter as OriginalResetter;
1112
use Magento\Framework\ObjectManagerInterface;
1213
use WeakMap;
@@ -33,15 +34,15 @@ class Resetter extends OriginalResetter
3334
/**
3435
* Constructor
3536
*
36-
* @param ComponentRegistrarInterface $componentRegistrar
37+
* @param ComponentRegistrarInterface|null $componentRegistrar
3738
* @param array $classList
3839
* @return void
3940
*/
40-
public function __construct()
41+
public function __construct(?ComponentRegistrarInterface $componentRegistrar = null, array $classList = [])
4142
{
4243
$this->collectedWeakMap = new WeakMap;
4344
$this->skipListAndFilterList = new SkipListAndFilterList;
44-
parent::__construct();
45+
parent::__construct($componentRegistrar, $classList);
4546
}
4647

4748
/**

lib/internal/Magento/Framework/Url/QueryParamsResolver.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
*/
66
namespace Magento\Framework\Url;
77

8-
class QueryParamsResolver extends \Magento\Framework\DataObject implements QueryParamsResolverInterface
8+
use Magento\Framework\ObjectManager\ResetAfterRequestInterface;
9+
10+
class QueryParamsResolver extends \Magento\Framework\DataObject implements
11+
QueryParamsResolverInterface,
12+
ResetAfterRequestInterface
913
{
1014
/**
1115
* @inheritdoc

0 commit comments

Comments
 (0)