7
7
8
8
namespace Magento \Framework \ObjectManager \Resetter ;
9
9
10
- use Magento \Framework \App \ObjectManager ;
10
+ use Magento \Framework \Component \ComponentRegistrar ;
11
+ use Magento \Framework \Component \ComponentRegistrarInterface ;
12
+ use Magento \Framework \Exception \LocalizedException ;
11
13
use Magento \Framework \ObjectManager \ResetAfterRequestInterface ;
12
14
use Magento \Framework \ObjectManagerInterface ;
13
15
use WeakMap ;
17
19
*/
18
20
class Resetter implements ResetterInterface
19
21
{
20
- public const RESET_PATH = '/app/etc/reset.php ' ;
22
+ public const RESET_PATH = 'reset.json ' ;
23
+ private const RESET_STATE_METHOD = '_resetState ' ;
21
24
22
25
/** @var WeakMap instances to be reset after request */
23
26
private WeakMap $ resetAfterWeakMap ;
@@ -28,40 +31,56 @@ class Resetter implements ResetterInterface
28
31
/** @var WeakMapSorter|null Note: We use temporal coupling here because of chicken/egg during bootstrapping */
29
32
private ?WeakMapSorter $ weakMapSorter = null ;
30
33
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 = [];
46
45
47
46
/**
48
- * @var array
47
+ * @param ComponentRegistrarInterface|null $componentRegistrar
48
+ * @param array $classList
49
+ * @return void
50
+ * @phpcs:disable Magento2.Functions.DiscouragedFunction
49
51
*/
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
+ }
51
72
52
73
/**
53
- * Constructor
74
+ * Get paths for reset json
54
75
*
55
- * @return void
56
- * @phpcs:disable Magento2.Functions.DiscouragedFunction
76
+ * @return \Generator<string>
57
77
*/
58
- public function __construct ()
78
+ private function getPaths (): \ Generator
59
79
{
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 ;
63
83
}
64
- $ this ->resetAfterWeakMap = new WeakMap ;
65
84
}
66
85
67
86
/**
@@ -72,7 +91,10 @@ public function __construct()
72
91
*/
73
92
public function addInstance (object $ instance ) : void
74
93
{
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
+ ) {
76
98
$ this ->resetAfterWeakMap [$ instance ] = true ;
77
99
}
78
100
}
@@ -116,37 +138,124 @@ public function setObjectManager(ObjectManagerInterface $objectManager) : void
116
138
}
117
139
118
140
/**
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)
120
166
*
121
167
* @param object $instance
122
168
* @return void
123
169
* @throws \ReflectionException
170
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
124
171
*/
125
172
private function resetStateWithReflection (object $ instance )
126
173
{
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 ] ?? [];
128
251
$ reflectionClass = $ this ->reflectionCache [$ className ]
129
252
?? $ this ->reflectionCache [$ className ] = new \ReflectionClass ($ className );
130
253
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
- }
138
254
$ name = $ property ->getName ();
139
- if (!in_array ( $ type , [ ' bool ' , ' array ' , ' null ' , ' true ' , ' false ' ], true )) {
255
+ if (!array_key_exists ( $ name , $ classResetValues )) {
140
256
continue ;
141
257
}
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 ];
150
259
$ property ->setAccessible (true );
151
260
$ property ->setValue ($ instance , $ value );
152
261
$ property ->setAccessible (false );
0 commit comments