@@ -26,6 +26,11 @@ class UpToDateDeclarativeSchema implements UpToDateValidatorInterface
2626 */
2727 private $ schemaDiff ;
2828
29+ /**
30+ * @var array|null
31+ */
32+ private $ cachedDiff = null ;
33+
2934 /**
3035 * UpToDateSchema constructor.
3136 * @param SchemaConfigInterface $schemaConfig
@@ -52,9 +57,235 @@ public function getNotUpToDateMessage() : string
5257 */
5358 public function isUpToDate () : bool
5459 {
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 ;
59290 }
60291}
0 commit comments