Skip to content

Commit 071fe39

Browse files
committed
AC-14807::Backport Verbose Mode in setup:db:status CLI command
1 parent 7da46f5 commit 071fe39

File tree

7 files changed

+354
-6
lines changed

7 files changed

+354
-6
lines changed

lib/internal/Magento/Framework/Setup/Declaration/Schema/UpToDateDeclarativeSchema.php

Lines changed: 235 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

lib/internal/Magento/Framework/Setup/OldDbValidator.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,22 @@ public function isUpToDate(): bool
7373
{
7474
return empty($this->dbVersionInfo->getDbVersionErrors());
7575
}
76+
77+
/**
78+
* Get detailed information about database version errors
79+
*
80+
* @return array
81+
*/
82+
public function getDetails() : array
83+
{
84+
$versionErrors = $this->dbVersionInfo->getDbVersionErrors();
85+
if (empty($versionErrors)) {
86+
return [];
87+
}
88+
89+
return [
90+
'timestamp' => date('Y-m-d H:i:s'),
91+
'version_errors' => $versionErrors
92+
];
93+
}
7694
}

lib/internal/Magento/Framework/Setup/Patch/UpToDateData.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,35 @@ public function isUpToDate() : bool
7979

8080
return true;
8181
}
82+
83+
/**
84+
* Get detailed information about unapplied data patches
85+
*
86+
* @return array
87+
*/
88+
public function getDetails(): array
89+
{
90+
$unappliedPatches = [];
91+
92+
foreach ($this->moduleList->getNames() as $moduleName) {
93+
foreach ($this->patchReader->read($moduleName) as $patchName) {
94+
if (!$this->patchBackwardCompatability->isSkipableByDataSetupVersion($patchName, $moduleName) &&
95+
!$this->patchHistory->isApplied($patchName)) {
96+
$unappliedPatches[] = [
97+
'patch' => $patchName,
98+
'module' => $moduleName
99+
];
100+
}
101+
}
102+
}
103+
104+
if (empty($unappliedPatches)) {
105+
return [];
106+
}
107+
108+
return [
109+
'timestamp' => date('Y-m-d H:i:s'),
110+
'unapplied_patches' => $unappliedPatches
111+
];
112+
}
82113
}

lib/internal/Magento/Framework/Setup/Patch/UpToDateSchema.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,35 @@ public function isUpToDate() : bool
7878

7979
return true;
8080
}
81+
82+
/**
83+
* Get detailed information about unapplied schema patches
84+
*
85+
* @return array
86+
*/
87+
public function getDetails(): array
88+
{
89+
$unappliedPatches = [];
90+
91+
foreach ($this->moduleList->getNames() as $moduleName) {
92+
foreach ($this->patchReader->read($moduleName) as $patchName) {
93+
if (!$this->patchBackwardCompatability->isSkipableBySchemaSetupVersion($patchName, $moduleName) &&
94+
!$this->patchHistory->isApplied($patchName)) {
95+
$unappliedPatches[] = [
96+
'patch' => $patchName,
97+
'module' => $moduleName
98+
];
99+
}
100+
}
101+
}
102+
103+
if (empty($unappliedPatches)) {
104+
return [];
105+
}
106+
107+
return [
108+
'timestamp' => date('Y-m-d H:i:s'),
109+
'unapplied_patches' => $unappliedPatches
110+
];
111+
}
81112
}

lib/internal/Magento/Framework/Setup/UpToDateValidatorInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,11 @@ public function getNotUpToDateMessage() : string ;
3535
* @return bool
3636
*/
3737
public function isUpToDate() : bool ;
38+
39+
/**
40+
* Retrieve detailed information about validator state and differences found
41+
*
42+
* @return array
43+
*/
44+
public function getDetails() : array;
3845
}

setup/src/Magento/Setup/Console/Command/DbStatusCommand.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ protected function configure()
8282
*/
8383
protected function execute(InputInterface $input, OutputInterface $output)
8484
{
85+
$timestamp = date('Y-m-d H:i:s');
86+
$output->writeln("<info>DbStatusCommand execution started at {$timestamp}</info>");
87+
8588
if (!$this->deploymentConfig->isAvailable()) {
8689
$output->writeln(
8790
"<info>No information is available: the Magento application is not installed.</info>"
@@ -92,8 +95,27 @@ protected function execute(InputInterface $input, OutputInterface $output)
9295
$outDated = false;
9396

9497
foreach ($this->upToDateValidators as $validator) {
95-
if (!$validator->isUpToDate()) {
96-
$output->writeln(sprintf('<info>%s</info>', $validator->getNotUpToDateMessage()));
98+
$validatorClass = get_class($validator);
99+
100+
try {
101+
$isUpToDate = $validator->isUpToDate();
102+
$output->writeln("<info>Validator {$validatorClass} isUpToDate: " . ($isUpToDate ? 'true' : 'false') . "</info>");
103+
104+
if (!$isUpToDate) {
105+
$message = $validator->getNotUpToDateMessage();
106+
$output->writeln(sprintf('<info>%s</info>', $message));
107+
108+
$details = $validator->getDetails();
109+
if (!empty($details)) {
110+
$detailsJson = json_encode($details, JSON_PRETTY_PRINT);
111+
$output->writeln(sprintf('<info>Details: %s</info>', $detailsJson));
112+
}
113+
114+
$outDated = true;
115+
}
116+
} catch (\Throwable $e) {
117+
$output->writeln("<info>Validator {$validatorClass} failed with error: " . $e->getMessage() . "</info>");
118+
$output->writeln("<info>Treating as upgrade required due to validation error.</info>");
97119
$outDated = true;
98120
}
99121
}

0 commit comments

Comments
 (0)