|
27 | 27 | use Cake\Database\Schema\ForeignKey; |
28 | 28 | use Cake\Database\Schema\Index; |
29 | 29 | use Cake\Database\Schema\TableSchema; |
| 30 | +use Cake\Database\Schema\TableSchemaInterface; |
30 | 31 | use Cake\Database\Schema\UniqueKey; |
31 | 32 | use Cake\Datasource\ConnectionManager; |
32 | 33 | use Cake\Event\Event; |
33 | 34 | use Cake\Event\EventManager; |
| 35 | +use Error; |
34 | 36 | use Migrations\Migration\ManagerFactory; |
35 | 37 | use Migrations\Util\TableFinder; |
36 | 38 | use Migrations\Util\UtilTrait; |
| 39 | +use ReflectionException; |
| 40 | +use ReflectionProperty; |
37 | 41 |
|
38 | 42 | /** |
39 | 43 | * Task class for generating migration diff files. |
@@ -274,8 +278,8 @@ protected function getColumns(): void |
274 | 278 |
|
275 | 279 | // changes in columns meta-data |
276 | 280 | foreach ($currentColumns as $columnName) { |
277 | | - $column = $currentSchema->getColumn($columnName); |
278 | | - $oldColumn = $this->dumpSchema[$table]->getColumn($columnName); |
| 281 | + $column = $this->safeGetColumn($currentSchema, $columnName); |
| 282 | + $oldColumn = $this->safeGetColumn($this->dumpSchema[$table], $columnName); |
279 | 283 | unset( |
280 | 284 | $column['collate'], |
281 | 285 | $column['fixed'], |
@@ -621,6 +625,67 @@ public function template(): string |
621 | 625 | return 'Migrations.config/diff'; |
622 | 626 | } |
623 | 627 |
|
| 628 | + /** |
| 629 | + * Safely get column information from a TableSchema. |
| 630 | + * |
| 631 | + * This method handles the case where Column::$fixed property may not be |
| 632 | + * initialized (e.g., when loaded from a cached/serialized schema). |
| 633 | + * |
| 634 | + * @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema |
| 635 | + * @param string $columnName The column name |
| 636 | + * @return array<string, mixed>|null Column data array or null if column doesn't exist |
| 637 | + */ |
| 638 | + protected function safeGetColumn(TableSchemaInterface $schema, string $columnName): ?array |
| 639 | + { |
| 640 | + try { |
| 641 | + return $schema->getColumn($columnName); |
| 642 | + } catch (Error $e) { |
| 643 | + // Handle uninitialized typed property errors (e.g., Column::$fixed) |
| 644 | + // This can happen with cached/serialized schema objects |
| 645 | + if (str_contains($e->getMessage(), 'must not be accessed before initialization')) { |
| 646 | + // Initialize uninitialized properties using reflection and retry |
| 647 | + $this->initializeColumnProperties($schema, $columnName); |
| 648 | + |
| 649 | + return $schema->getColumn($columnName); |
| 650 | + } |
| 651 | + throw $e; |
| 652 | + } |
| 653 | + } |
| 654 | + |
| 655 | + /** |
| 656 | + * Initialize potentially uninitialized Column properties using reflection. |
| 657 | + * |
| 658 | + * @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema |
| 659 | + * @param string $columnName The column name |
| 660 | + * @return void |
| 661 | + */ |
| 662 | + protected function initializeColumnProperties(TableSchemaInterface $schema, string $columnName): void |
| 663 | + { |
| 664 | + // Access the internal columns array via reflection |
| 665 | + $reflection = new ReflectionProperty($schema, '_columns'); |
| 666 | + $columns = $reflection->getValue($schema); |
| 667 | + |
| 668 | + if (!isset($columns[$columnName]) || !($columns[$columnName] instanceof Column)) { |
| 669 | + return; |
| 670 | + } |
| 671 | + |
| 672 | + $column = $columns[$columnName]; |
| 673 | + |
| 674 | + // List of nullable properties that might not be initialized |
| 675 | + $nullableProperties = ['fixed', 'collate', 'unsigned', 'generated', 'srid', 'onUpdate']; |
| 676 | + |
| 677 | + foreach ($nullableProperties as $propertyName) { |
| 678 | + try { |
| 679 | + $propReflection = new ReflectionProperty(Column::class, $propertyName); |
| 680 | + if (!$propReflection->isInitialized($column)) { |
| 681 | + $propReflection->setValue($column, null); |
| 682 | + } |
| 683 | + } catch (Error | ReflectionException) { |
| 684 | + // Property doesn't exist or can't be accessed, skip it |
| 685 | + } |
| 686 | + } |
| 687 | + } |
| 688 | + |
624 | 689 | /** |
625 | 690 | * Gets the option parser instance and configures it. |
626 | 691 | * |
|
0 commit comments