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 \SemanticVersionChecker \Analyzer ;
9+
10+ use PhpParser \Node \Stmt ;
11+ use PHPSemVerChecker \Registry \Registry ;
12+ use PHPSemVerChecker \Report \Report ;
13+ use PHPSemVerChecker \SemanticVersioning \Level ;
14+ use Magento \SemanticVersionChecker \Operation \EtSchema \EtSchemaOperation ;
15+
16+ /**
17+ * Class EtSchemaAnalyzer analyzes changes in et_schema.xml
18+ */
19+ class EtSchemaAnalyzer implements AnalyzerInterface
20+ {
21+ private static $ actions = [
22+ 'addedRecord ' => [
23+ 'level ' => Level::MINOR ,
24+ 'code ' => 'T004 ' ,
25+ 'message ' => 'Added a new declaration for record %s. '
26+ ],
27+ 'removedRecord ' => [
28+ 'level ' => Level::MAJOR ,
29+ 'code ' => 'T001 ' ,
30+ 'message ' => 'Removed declaration for type %s. '
31+ ],
32+ 'addedField ' => [
33+ 'level ' => Level::PATCH ,
34+ 'code ' => 'T005 ' ,
35+ 'message ' => 'Added field %s to type %s. '
36+ ],
37+ 'removedField ' => [
38+ 'level ' => Level::MAJOR ,
39+ 'code ' => 'T002 ' ,
40+ 'message ' => 'Removed field %s from type %s. '
41+ ],
42+ 'changedField ' => [
43+ 'level ' => Level::MAJOR ,
44+ 'code ' => 'T003 ' ,
45+ 'message ' => 'Changed field %s declaration in type %s. '
46+ ]
47+ ];
48+
49+ /**
50+ * @var string
51+ */
52+ private $ context = 'etSchema ' ;
53+
54+ /**
55+ * @var Report
56+ */
57+ private $ report ;
58+
59+ /**
60+ * Constructor.
61+ *
62+ * @param Report $report
63+ */
64+ public function __construct (Report $ report )
65+ {
66+ $ this ->report = $ report ;
67+ }
68+
69+ private function reportAddedModuleConfig (array $ moduleConfig ): array
70+ {
71+ return [];
72+ }
73+
74+ private function removedModuleConfig (array $ moduleConfig ): array
75+ {
76+ return [];
77+ }
78+
79+ /**
80+ * Register record creation
81+ *
82+ * @param string $moduleName
83+ * @param string $recordName
84+ * @return array
85+ */
86+ private function addedRecord (string $ moduleName , string $ recordName ): array
87+ {
88+ return [
89+ 'level ' => self ::$ actions [__FUNCTION__ ]['level ' ],
90+ 'code ' => self ::$ actions [__FUNCTION__ ]['code ' ],
91+ 'location ' => sprintf ('urn:magento:module:%s:etc/et_schema.xml %s ' , $ moduleName , $ recordName ),
92+ 'target ' => $ recordName ,
93+ 'reason ' => sprintf (self ::$ actions [__FUNCTION__ ]['message ' ], $ recordName )
94+ ];
95+ }
96+
97+ /**
98+ * Register record removal
99+ *
100+ * @param string $moduleName
101+ * @param string $recordName
102+ * @return array
103+ */
104+ private function removedRecord (string $ moduleName , string $ recordName ): array
105+ {
106+ return [
107+ 'level ' => self ::$ actions [__FUNCTION__ ]['level ' ],
108+ 'code ' => self ::$ actions [__FUNCTION__ ]['code ' ],
109+ 'location ' => sprintf ('urn:magento:module:%s:etc/et_schema.xml %s ' , $ moduleName , $ recordName ),
110+ 'target ' => $ recordName ,
111+ 'reason ' => sprintf (self ::$ actions [__FUNCTION__ ]['message ' ], $ recordName )
112+ ];
113+ }
114+
115+ /**
116+ * @param string $moduleName
117+ * @param string $recordName
118+ * @param string $fieldName
119+ * @return array
120+ */
121+ private function removedField (string $ moduleName , string $ recordName , string $ fieldName ): array
122+ {
123+ return [
124+ 'level ' => self ::$ actions [__FUNCTION__ ]['level ' ],
125+ 'code ' => self ::$ actions [__FUNCTION__ ]['code ' ],
126+ 'location ' => sprintf (
127+ 'urn:magento:module:%s:etc/et_schema.xml %s:%s ' ,
128+ $ moduleName ,
129+ $ recordName ,
130+ $ fieldName
131+ ),
132+ 'target ' => $ recordName ,
133+ 'reason ' => sprintf (self ::$ actions [__FUNCTION__ ]['message ' ], $ fieldName , $ recordName )
134+ ];
135+ }
136+
137+ /**
138+ * @param string $moduleName
139+ * @param string $recordName
140+ * @param string $fieldName
141+ * @return array
142+ */
143+ private function addedField (string $ moduleName , string $ recordName , string $ fieldName ): array
144+ {
145+ return [
146+ 'level ' => self ::$ actions [__FUNCTION__ ]['level ' ],
147+ 'code ' => self ::$ actions [__FUNCTION__ ]['code ' ],
148+ 'location ' => sprintf (
149+ 'urn:magento:module:%s:etc/et_schema.xml %s:%s ' ,
150+ $ moduleName ,
151+ $ recordName ,
152+ $ fieldName
153+ ),
154+ 'target ' => $ recordName ,
155+ 'reason ' => sprintf (self ::$ actions [__FUNCTION__ ]['message ' ], $ fieldName , $ recordName )
156+ ];
157+ }
158+
159+ /**
160+ * @param string $moduleName
161+ * @param string $recordName
162+ * @param string $fieldName
163+ * @return array
164+ */
165+ private function changedField (string $ moduleName , string $ recordName , string $ fieldName ): array
166+ {
167+ return [
168+ 'level ' => self ::$ actions [__FUNCTION__ ]['level ' ],
169+ 'code ' => self ::$ actions [__FUNCTION__ ]['code ' ],
170+ 'location ' => sprintf (
171+ 'urn:magento:module:%s:etc/et_schema.xml %s:%s ' ,
172+ $ moduleName ,
173+ $ recordName ,
174+ $ fieldName
175+ ),
176+ 'target ' => $ recordName ,
177+ 'reason ' => sprintf (self ::$ actions [__FUNCTION__ ]['message ' ], $ fieldName , $ recordName )
178+ ];
179+ }
180+
181+ /**
182+ * Analyze record structure
183+ *
184+ * @param string $moduleName
185+ * @param $beforeRecord
186+ * @param $afterRecord
187+ * @return array
188+ */
189+ private function analyzeRecord (string $ moduleName , $ beforeRecord , $ afterRecord ): array
190+ {
191+ $ changes = [];
192+ $ commonFields = array_intersect (
193+ array_keys ($ beforeRecord ['field ' ]),
194+ array_keys ($ afterRecord ['field ' ])
195+ );
196+ foreach ($ commonFields as $ fieldName ) {
197+ if ($ beforeRecord ['field ' ][$ fieldName ]['type ' ] != $ afterRecord ['field ' ][$ fieldName ]['type ' ]
198+ || $ beforeRecord ['field ' ][$ fieldName ]['repeated ' ] != $ afterRecord ['field ' ][$ fieldName ]['repeated ' ]
199+ ) {
200+ $ this ->changedField ($ moduleName , $ beforeRecord ['name ' ], $ fieldName );
201+ }
202+ }
203+ $ diff = array_merge (
204+ array_diff (
205+ array_keys ($ beforeRecord ['field ' ]),
206+ array_keys ($ afterRecord ['field ' ])
207+ ),
208+ array_diff (
209+ array_keys ($ afterRecord ['field ' ]),
210+ array_keys ($ beforeRecord ['field ' ])
211+ )
212+ );
213+ foreach ($ diff as $ fieldName ) {
214+ if (isset ($ beforeRecord ['field ' ][$ fieldName ])) {
215+ $ changes [] = $ this ->removedField ($ moduleName , $ beforeRecord ['name ' ], $ fieldName );
216+ } else {
217+ $ changes [] = $ this ->addedField ($ moduleName , $ afterRecord ['name ' ], $ fieldName );
218+ }
219+ }
220+ return $ changes ;
221+ }
222+
223+ /**
224+ * Analyze module configuration file
225+ *
226+ * @param string $moduleName
227+ * @param array $beforeModuleConfig
228+ * @param array $afterModuleConfig
229+ * @return array
230+ */
231+ private function analyzeModuleConfig (string $ moduleName , array $ beforeModuleConfig , array $ afterModuleConfig ): array
232+ {
233+ $ changes = [];
234+ $ commonRecords = array_intersect (
235+ array_keys ($ beforeModuleConfig ),
236+ array_keys ($ afterModuleConfig )
237+ );
238+ foreach ($ commonRecords as $ recordName ) {
239+ $ changes += $ this ->analyzeRecord (
240+ $ moduleName ,
241+ $ beforeModuleConfig [$ recordName ],
242+ $ afterModuleConfig [$ recordName ]
243+ );
244+ }
245+ $ diff = array_merge (
246+ array_diff (
247+ array_keys ($ beforeModuleConfig ),
248+ array_keys ($ afterModuleConfig )
249+ ),
250+ array_diff (
251+ array_keys ($ afterModuleConfig ),
252+ array_keys ($ beforeModuleConfig )
253+ )
254+ );
255+ foreach ($ diff as $ recordName ) {
256+ if (isset ($ beforeModuleConfig [$ recordName ])) {
257+ $ changes [] = $ this ->removedRecord ($ moduleName , $ recordName );
258+ } else {
259+ $ changes [] = $ this ->addedRecord ($ moduleName , $ recordName );
260+ }
261+ }
262+ return $ changes ;
263+ }
264+
265+ /**
266+ * Register changes to the report
267+ *
268+ * @param array $changes
269+ */
270+ public function reportChanges (array $ changes ): void
271+ {
272+ foreach ($ changes as $ change ) {
273+ $ this ->report ->add (
274+ $ this ->context ,
275+ new EtSchemaOperation (
276+ $ change ['location ' ],
277+ $ change ['code ' ],
278+ $ change ['target ' ],
279+ $ change ['reason ' ],
280+ $ change ['level ' ]
281+ )
282+ );
283+ }
284+ }
285+
286+ /**
287+ * Analyze configuration changes
288+ *
289+ * @param Stmt|Registry $registryBefore
290+ * @param Stmt|Registry $registryAfter
291+ * @return Report
292+ */
293+ public function analyze ($ registryBefore , $ registryAfter )
294+ {
295+ $ changes = [];
296+ $ commonModules = array_intersect (
297+ array_keys ($ registryBefore ->data ['etSchema ' ]),
298+ array_keys ($ registryAfter ->data ['etSchema ' ])
299+ );
300+ foreach ($ commonModules as $ moduleName ) {
301+ $ changes += $ this ->analyzeModuleConfig (
302+ $ moduleName ,
303+ $ registryBefore ->data ['etSchema ' ][$ moduleName ],
304+ $ registryAfter ->data ['etSchema ' ][$ moduleName ]
305+ );
306+ }
307+ $ this ->reportChanges ($ changes );
308+ return $ this ->report ;
309+ }
310+ }
0 commit comments