@@ -26,6 +26,11 @@ class UpToDateDeclarativeSchema implements UpToDateValidatorInterface
26
26
*/
27
27
private $ schemaDiff ;
28
28
29
+ /**
30
+ * @var array|null
31
+ */
32
+ private $ cachedDiff = null ;
33
+
29
34
/**
30
35
* UpToDateSchema constructor.
31
36
* @param SchemaConfigInterface $schemaConfig
@@ -52,9 +57,235 @@ public function getNotUpToDateMessage() : string
52
57
*/
53
58
public function isUpToDate () : bool
54
59
{
55
- $ declarativeSchema = $ this ->schemaConfig ->getDeclarationConfig ();
56
- $ dbSchema = $ this ->schemaConfig ->getDbConfig ();
57
- $ diff = $ this ->schemaDiff ->diff ($ declarativeSchema , $ dbSchema );
58
- return empty ($ diff ->getAll ());
60
+ return empty ($ this ->calculateDiff ());
61
+ }
62
+
63
+ /**
64
+ * Get detailed information about schema differences
65
+ *
66
+ * @return array
67
+ */
68
+ public function getDetails () : array
69
+ {
70
+ $ diffData = $ this ->calculateDiff ();
71
+ $ summary = $ this ->buildSummary ($ diffData );
72
+ $ summary ['timestamp ' ] = date ('Y-m-d H:i:s ' );
73
+
74
+ return $ summary ;
75
+ }
76
+
77
+ /**
78
+ * Calculate schema differences and cache the result
79
+ *
80
+ * @return array
81
+ */
82
+ private function calculateDiff () : array
83
+ {
84
+ if ($ this ->cachedDiff === null ) {
85
+ $ declarativeSchema = $ this ->schemaConfig ->getDeclarationConfig ();
86
+ $ dbSchema = $ this ->schemaConfig ->getDbConfig ();
87
+ $ diff = $ this ->schemaDiff ->diff ($ declarativeSchema , $ dbSchema );
88
+ $ this ->cachedDiff = $ diff ->getAll () ?? [];
89
+ }
90
+
91
+ return $ this ->cachedDiff ;
92
+ }
93
+
94
+ /**
95
+ * Build a summary of schema differences
96
+ *
97
+ * @param array $diffData
98
+ * @return array
99
+ */
100
+ private function buildSummary (array $ diffData ) : array
101
+ {
102
+ $ summary = [
103
+ 'timestamp ' => date ('Y-m-d H:i:s ' ),
104
+ 'total_differences ' => 0 ,
105
+ 'by_change_type ' => [],
106
+ 'affected_tables ' => [],
107
+ 'changes ' => []
108
+ ];
109
+
110
+ try {
111
+ foreach ($ diffData as $ key => $ operations ) {
112
+ if (!is_array ($ operations )) {
113
+ continue ;
114
+ }
115
+
116
+ foreach ($ operations as $ operationType => $ changes ) {
117
+ if (!isset ($ summary ['by_change_type ' ][$ operationType ])) {
118
+ $ summary ['by_change_type ' ][$ operationType ] = 0 ;
119
+ }
120
+
121
+ $ changeCount = is_array ($ changes ) ? count ($ changes ) : 1 ;
122
+ $ summary ['by_change_type ' ][$ operationType ] += $ changeCount ;
123
+ $ summary ['total_differences ' ] += $ changeCount ;
124
+
125
+ if (is_array ($ changes )) {
126
+ foreach ($ changes as $ changeIndex => $ change ) {
127
+ $ changeInfo = [
128
+ 'operation ' => $ operationType ,
129
+ 'index ' => $ changeIndex
130
+ ];
131
+
132
+ $ tableName = $ this ->safeGetTableName ($ change );
133
+ if ($ tableName ) {
134
+ $ changeInfo ['table ' ] = $ tableName ;
135
+
136
+ if (!isset ($ summary ['affected_tables ' ][$ tableName ])) {
137
+ $ summary ['affected_tables ' ][$ tableName ] = [];
138
+ }
139
+
140
+ if (!isset ($ summary ['affected_tables ' ][$ tableName ][$ operationType ])) {
141
+ $ summary ['affected_tables ' ][$ tableName ][$ operationType ] = 0 ;
142
+ }
143
+
144
+ $ summary ['affected_tables ' ][$ tableName ][$ operationType ]++;
145
+ }
146
+
147
+ // Add any other safely extractable information
148
+ if ($ change instanceof ElementHistory) {
149
+ $ changeInfo = $ this ->processElementHistory ($ change , $ changeInfo );
150
+ } elseif (is_array ($ change ) && isset ($ change ['name ' ])) {
151
+ $ changeInfo ['name ' ] = $ change ['name ' ];
152
+ } elseif (is_object ($ change ) && method_exists ($ change , 'getName ' )) {
153
+ $ changeInfo ['name ' ] = $ change ->getName ();
154
+
155
+ // Special handling for index elements
156
+ if (method_exists ($ change , 'getType ' ) && ($ change ->getType () === 'index ' || $ change ->getType () === 'constraint ' )) {
157
+ $ changeInfo ['type ' ] = $ change ->getType ();
158
+
159
+ // Try to get the index columns if available
160
+ if (method_exists ($ change , 'getColumns ' )) {
161
+ $ columns = $ change ->getColumns ();
162
+ if (is_array ($ columns )) {
163
+ $ changeInfo ['columns ' ] = [];
164
+ foreach ($ columns as $ column ) {
165
+ if (is_object ($ column ) && method_exists ($ column , 'getName ' )) {
166
+ $ changeInfo ['columns ' ][] = $ column ->getName ();
167
+ } elseif (is_string ($ column )) {
168
+ $ changeInfo ['columns ' ][] = $ column ;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ $ summary ['changes ' ][] = $ changeInfo ;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ } catch (\Exception $ e ) {
182
+ $ summary ['error ' ] = $ e ->getMessage ();
183
+ }
184
+
185
+ return $ summary ;
186
+ }
187
+
188
+ /**
189
+ * Safely get table name from any change object
190
+ *
191
+ * @param mixed $change
192
+ * @return string|null
193
+ */
194
+ private function safeGetTableName ($ change ): ?string
195
+ {
196
+ try {
197
+ // Option 1: ElementHistory with getNew() or getOld()
198
+ if ($ change instanceof ElementHistory) {
199
+ $ element = $ change ->getNew () ?: $ change ->getOld ();
200
+ if ($ element ) {
201
+ // If element is a table
202
+ if (method_exists ($ element , 'getType ' ) && $ element ->getType () === 'table ' &&
203
+ method_exists ($ element , 'getName ' )) {
204
+ return $ element ->getName ();
205
+ }
206
+
207
+ // If element belongs to a table
208
+ if (method_exists ($ element , 'getTable ' )) {
209
+ $ table = $ element ->getTable ();
210
+ if ($ table && method_exists ($ table , 'getName ' )) {
211
+ return $ table ->getName ();
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ // Option 2: Array with 'table' key
218
+ if (is_array ($ change ) && isset ($ change ['table ' ])) {
219
+ return $ change ['table ' ];
220
+ }
221
+
222
+ // Option 3: Object with getTable() method
223
+ if (is_object ($ change ) && method_exists ($ change , 'getTable ' )) {
224
+ $ table = $ change ->getTable ();
225
+ if (is_string ($ table )) {
226
+ return $ table ;
227
+ } elseif (is_object ($ table ) && method_exists ($ table , 'getName ' )) {
228
+ return $ table ->getName ();
229
+ }
230
+ }
231
+
232
+ // Option 4: Object is itself a table
233
+ if (is_object ($ change ) && method_exists ($ change , 'getType ' ) &&
234
+ $ change ->getType () === 'table ' && method_exists ($ change , 'getName ' )) {
235
+ return $ change ->getName ();
236
+ }
237
+ } catch (\Exception $ e ) {
238
+ // Silently fail and return null
239
+ }
240
+
241
+ return null ;
242
+ }
243
+
244
+ /**
245
+ * Process ElementHistory object to extract useful information
246
+ *
247
+ * @param ElementHistory $change
248
+ * @param array $changeInfo
249
+ * @return array
250
+ */
251
+ private function processElementHistory ($ change , array $ changeInfo ): array
252
+ {
253
+ try {
254
+ $ newElement = $ change ->getNew ();
255
+ $ oldElement = $ change ->getOld ();
256
+
257
+ // Get element name
258
+ if ($ newElement && method_exists ($ newElement , 'getName ' )) {
259
+ $ changeInfo ['name ' ] = $ newElement ->getName ();
260
+ } elseif ($ oldElement && method_exists ($ oldElement , 'getName ' )) {
261
+ $ changeInfo ['name ' ] = $ oldElement ->getName ();
262
+ }
263
+
264
+ // Get element type
265
+ if ($ newElement && method_exists ($ newElement , 'getType ' )) {
266
+ $ changeInfo ['type ' ] = $ newElement ->getType ();
267
+ } elseif ($ oldElement && method_exists ($ oldElement , 'getType ' )) {
268
+ $ changeInfo ['type ' ] = $ oldElement ->getType ();
269
+ }
270
+
271
+ // For modify operations, add basic diff information
272
+ if (($ changeInfo ['operation ' ] === 'modify_column ' || $ changeInfo ['operation ' ] === 'modify_table ' ) && $ oldElement && $ newElement ) {
273
+ // Check for comment differences (most common issue)
274
+ if (method_exists ($ oldElement , 'getComment ' ) && method_exists ($ newElement , 'getComment ' )) {
275
+ $ oldComment = $ oldElement ->getComment ();
276
+ $ newComment = $ newElement ->getComment ();
277
+
278
+ if ($ oldComment !== $ newComment ) {
279
+ $ changeInfo ['comment_changed ' ] = true ;
280
+ $ changeInfo ['old_comment ' ] = $ oldComment ;
281
+ $ changeInfo ['new_comment ' ] = $ newComment ;
282
+ }
283
+ }
284
+ }
285
+ } catch (\Exception $ e ) {
286
+ // Silently fail and return original changeInfo
287
+ }
288
+
289
+ return $ changeInfo ;
59
290
}
60
291
}
0 commit comments