1
1
<?php
2
2
/**
3
- * Copyright © Magento, Inc. All rights reserved.
4
- * See COPYING.txt for license details .
3
+ * Copyright 2018 Adobe
4
+ * All Rights Reserved .
5
5
*/
6
6
7
7
declare (strict_types=1 );
10
10
11
11
use Magento \Framework \Setup \Declaration \Schema \Diff \SchemaDiff ;
12
12
use Magento \Framework \Setup \UpToDateValidatorInterface ;
13
+ use Magento \Framework \Setup \DetailProviderInterface ;
13
14
14
15
/**
15
16
* Allows to validate if schema is up to date or not
16
17
*/
17
- class UpToDateDeclarativeSchema implements UpToDateValidatorInterface
18
+ class UpToDateDeclarativeSchema implements UpToDateValidatorInterface, DetailProviderInterface
18
19
{
19
20
/**
20
21
* @var SchemaConfigInterface
@@ -26,6 +27,11 @@ class UpToDateDeclarativeSchema implements UpToDateValidatorInterface
26
27
*/
27
28
private $ schemaDiff ;
28
29
30
+ /**
31
+ * @var array|null
32
+ */
33
+ private $ cachedDiff = null ;
34
+
29
35
/**
30
36
* UpToDateSchema constructor.
31
37
* @param SchemaConfigInterface $schemaConfig
@@ -40,6 +46,8 @@ public function __construct(
40
46
}
41
47
42
48
/**
49
+ * Get the message
50
+ *
43
51
* @return string
44
52
*/
45
53
public function getNotUpToDateMessage () : string
@@ -48,13 +56,281 @@ public function getNotUpToDateMessage() : string
48
56
}
49
57
50
58
/**
59
+ * Check calculate schema differences
60
+ *
51
61
* @return bool
52
62
*/
53
63
public function isUpToDate () : bool
54
64
{
55
- $ declarativeSchema = $ this ->schemaConfig ->getDeclarationConfig ();
56
- $ dbSchema = $ this ->schemaConfig ->getDbConfig ();
57
- $ diff = $ this ->schemaDiff ->diff ($ declarativeSchema , $ dbSchema );
58
- return empty ($ diff ->getAll ());
65
+ return empty ($ this ->calculateDiff ());
66
+ }
67
+
68
+ /**
69
+ * Get detailed information about schema differences
70
+ *
71
+ * @return array
72
+ */
73
+ public function getDetails () : array
74
+ {
75
+ $ diffData = $ this ->calculateDiff ();
76
+ $ summary = $ this ->buildSummary ($ diffData );
77
+ $ summary ['timestamp ' ] = date ('Y-m-d H:i:s ' );
78
+
79
+ return $ summary ;
80
+ }
81
+
82
+ /**
83
+ * Calculate schema differences and cache the result
84
+ *
85
+ * @return array
86
+ */
87
+ private function calculateDiff () : array
88
+ {
89
+ if ($ this ->cachedDiff === null ) {
90
+ $ declarativeSchema = $ this ->schemaConfig ->getDeclarationConfig ();
91
+ $ dbSchema = $ this ->schemaConfig ->getDbConfig ();
92
+ $ diff = $ this ->schemaDiff ->diff ($ declarativeSchema , $ dbSchema );
93
+ $ this ->cachedDiff = $ diff ->getAll () ?? [];
94
+ }
95
+
96
+ return $ this ->cachedDiff ;
97
+ }
98
+
99
+ /**
100
+ * Build a summary of schema differences
101
+ *
102
+ * @param array $diffData
103
+ * @return array
104
+ */
105
+ private function buildSummary (array $ diffData ): array
106
+ {
107
+ $ summary = [
108
+ 'timestamp ' => date ('Y-m-d H:i:s ' ),
109
+ 'total_differences ' => 0 ,
110
+ 'by_change_type ' => [],
111
+ 'affected_tables ' => [],
112
+ 'changes ' => []
113
+ ];
114
+ try {
115
+ foreach ($ diffData as $ operations ) {
116
+ if (!is_array ($ operations )) {
117
+ continue ;
118
+ }
119
+ foreach ($ operations as $ operationType => $ changes ) {
120
+ $ this ->initChangeTypeCount ($ summary , $ operationType );
121
+
122
+ $ changeCount = is_array ($ changes ) ? count ($ changes ) : 1 ;
123
+ $ summary ['by_change_type ' ][$ operationType ] += $ changeCount ;
124
+ $ summary ['total_differences ' ] += $ changeCount ;
125
+
126
+ if (!is_array ($ changes )) {
127
+ continue ;
128
+ }
129
+
130
+ foreach ($ changes as $ changeIndex => $ change ) {
131
+ $ changeInfo = $ this ->buildChangeInfo ($ change , $ operationType , $ changeIndex , $ summary );
132
+ $ summary ['changes ' ][] = $ changeInfo ;
133
+ }
134
+ }
135
+ }
136
+ } catch (\Exception $ e ) {
137
+ $ summary ['error ' ] = $ e ->getMessage ();
138
+ }
139
+ return $ summary ;
140
+ }
141
+
142
+ /**
143
+ * Initialize the counter for a given operation type in the summary if not already set.
144
+ *
145
+ * @param array &$summary
146
+ * @param string $operationType
147
+ */
148
+ private function initChangeTypeCount (array &$ summary , string $ operationType ): void
149
+ {
150
+ if (!isset ($ summary ['by_change_type ' ][$ operationType ])) {
151
+ $ summary ['by_change_type ' ][$ operationType ] = 0 ;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Build a structured array with information about a single change operation.
157
+ *
158
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
159
+ * @param mixed $change
160
+ * @param string $operationType
161
+ * @param int|string $changeIndex
162
+ * @param array $summary
163
+ * @return array
164
+ */
165
+ private function buildChangeInfo ($ change , $ operationType , $ changeIndex , &$ summary ): array
166
+ {
167
+ $ changeInfo = [
168
+ 'operation ' => $ operationType ,
169
+ 'index ' => $ changeIndex
170
+ ];
171
+
172
+ $ tableName = $ this ->safeGetTableName ($ change );
173
+ if ($ tableName ) {
174
+ $ changeInfo ['table ' ] = $ tableName ;
175
+
176
+ if (!isset ($ summary ['affected_tables ' ][$ tableName ])) {
177
+ $ summary ['affected_tables ' ][$ tableName ] = [];
178
+ }
179
+ if (!isset ($ summary ['affected_tables ' ][$ tableName ][$ operationType ])) {
180
+ $ summary ['affected_tables ' ][$ tableName ][$ operationType ] = 0 ;
181
+ }
182
+ $ summary ['affected_tables ' ][$ tableName ][$ operationType ]++;
183
+ }
184
+
185
+ if ($ change instanceof ElementHistory) {
186
+ $ changeInfo = $ this ->processElementHistory ($ change , $ changeInfo );
187
+ } elseif (is_array ($ change ) && isset ($ change ['name ' ])) {
188
+ $ changeInfo ['name ' ] = $ change ['name ' ];
189
+ } elseif (is_object ($ change ) && method_exists ($ change , 'getName ' )) {
190
+ $ changeInfo ['name ' ] = $ change ->getName ();
191
+
192
+ if (method_exists ($ change , 'getType ' )) {
193
+ $ this ->isMethodExists ($ change , $ changeInfo );
194
+ }
195
+ }
196
+ return $ changeInfo ;
197
+ }
198
+
199
+ /**
200
+ * Build a structured array with method exist information.
201
+ *
202
+ * @param mixed $change
203
+ * @param array $changeInfo
204
+ */
205
+ private function isMethodExists (mixed $ change , array &$ changeInfo ): void
206
+ {
207
+ $ type = $ change ->getType ();
208
+ if ($ type === 'index ' || $ type === 'constraint ' ) {
209
+ $ changeInfo ['type ' ] = $ type ;
210
+ if (method_exists ($ change , 'getColumns ' )) {
211
+ $ columns = $ change ->getColumns ();
212
+ if (is_array ($ columns )) {
213
+ $ changeInfo ['columns ' ] = array_map (function ($ column ) {
214
+ if (is_object ($ column ) && method_exists ($ column , 'getName ' )) {
215
+ return $ column ->getName ();
216
+ }
217
+ return is_string ($ column ) ? $ column : null ;
218
+ }, $ columns );
219
+ // Remove any nulls if any invalid columns found
220
+ $ changeInfo ['columns ' ] = array_filter ($ changeInfo ['columns ' ]);
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Safely get table name from any change object
228
+ *
229
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
230
+ * @SuppressWarnings(PHPMD.NPathComplexity)
231
+ * @param mixed $change
232
+ * @return string|null
233
+ */
234
+ private function safeGetTableName ($ change ): ?string
235
+ {
236
+ try {
237
+ // Option 1: ElementHistory with getNew() or getOld()
238
+ if ($ change instanceof ElementHistory) {
239
+ $ element = $ change ->getNew () ?: $ change ->getOld ();
240
+ if ($ element ) {
241
+ // If element is a table
242
+ if (method_exists ($ element , 'getType ' ) && $ element ->getType () === 'table ' &&
243
+ method_exists ($ element , 'getName ' )) {
244
+ return $ element ->getName ();
245
+ }
246
+
247
+ // If element belongs to a table
248
+ if (method_exists ($ element , 'getTable ' )) {
249
+ $ table = $ element ->getTable ();
250
+ if ($ table && method_exists ($ table , 'getName ' )) {
251
+ return $ table ->getName ();
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // Option 2: Array with 'table' key
258
+ if (is_array ($ change ) && isset ($ change ['table ' ])) {
259
+ return $ change ['table ' ];
260
+ }
261
+
262
+ // Option 3: Object with getTable() method
263
+ if (is_object ($ change ) && method_exists ($ change , 'getTable ' )) {
264
+ $ table = $ change ->getTable ();
265
+ if (is_string ($ table )) {
266
+ return $ table ;
267
+ } elseif (is_object ($ table ) && method_exists ($ table , 'getName ' )) {
268
+ return $ table ->getName ();
269
+ }
270
+ }
271
+
272
+ // Option 4: Object is itself a table
273
+ if (is_object ($ change ) && method_exists ($ change , 'getType ' ) &&
274
+ $ change ->getType () === 'table ' && method_exists ($ change , 'getName ' )) {
275
+ return $ change ->getName ();
276
+ }
277
+ } catch (\Exception $ e ) {
278
+ // Silently fail and return null
279
+ error_log ('Error get table name: ' . $ e ->getMessage ());
280
+ }
281
+
282
+ return null ;
283
+ }
284
+
285
+ /**
286
+ * Process ElementHistory object to extract useful information
287
+ *
288
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
289
+ * @SuppressWarnings(PHPMD.NPathComplexity)
290
+ * @param ElementHistory $change
291
+ * @param array $changeInfo
292
+ * @return array
293
+ */
294
+ private function processElementHistory ($ change , array $ changeInfo ): array
295
+ {
296
+ try {
297
+ $ newElement = $ change ->getNew ();
298
+ $ oldElement = $ change ->getOld ();
299
+
300
+ // Get element name
301
+ if ($ newElement && method_exists ($ newElement , 'getName ' )) {
302
+ $ changeInfo ['name ' ] = $ newElement ->getName ();
303
+ } elseif ($ oldElement && method_exists ($ oldElement , 'getName ' )) {
304
+ $ changeInfo ['name ' ] = $ oldElement ->getName ();
305
+ }
306
+
307
+ // Get element type
308
+ if ($ newElement && method_exists ($ newElement , 'getType ' )) {
309
+ $ changeInfo ['type ' ] = $ newElement ->getType ();
310
+ } elseif ($ oldElement && method_exists ($ oldElement , 'getType ' )) {
311
+ $ changeInfo ['type ' ] = $ oldElement ->getType ();
312
+ }
313
+
314
+ // For modify operations, add basic diff information
315
+ if (($ changeInfo ['operation ' ] === 'modify_column ' || $ changeInfo ['operation ' ] === 'modify_table ' )
316
+ && $ oldElement && $ newElement ) {
317
+ // Check for comment differences (most common issue)
318
+ if (method_exists ($ oldElement , 'getComment ' ) && method_exists ($ newElement , 'getComment ' )) {
319
+ $ oldComment = $ oldElement ->getComment ();
320
+ $ newComment = $ newElement ->getComment ();
321
+
322
+ if ($ oldComment !== $ newComment ) {
323
+ $ changeInfo ['comment_changed ' ] = true ;
324
+ $ changeInfo ['old_comment ' ] = $ oldComment ;
325
+ $ changeInfo ['new_comment ' ] = $ newComment ;
326
+ }
327
+ }
328
+ }
329
+ } catch (\Exception $ e ) {
330
+ // Silently fail and return original changeInfo
331
+ error_log ('Error processing element history: ' . $ e ->getMessage ());
332
+ }
333
+
334
+ return $ changeInfo ;
59
335
}
60
336
}
0 commit comments