2727use Cake \Database \Schema \ForeignKey ;
2828use Cake \Database \Schema \Index ;
2929use Cake \Database \Schema \TableSchema ;
30+ use Cake \Database \Schema \TableSchemaInterface ;
3031use Cake \Database \Schema \UniqueKey ;
3132use Cake \Datasource \ConnectionManager ;
3233use Cake \Event \Event ;
3334use Cake \Event \EventManager ;
35+ use Error ;
3436use Migrations \Migration \ManagerFactory ;
3537use Migrations \Util \TableFinder ;
3638use Migrations \Util \UtilTrait ;
39+ use ReflectionException ;
40+ use ReflectionProperty ;
3741
3842/**
3943 * Task class for generating migration diff files.
@@ -259,7 +263,7 @@ protected function getColumns(): void
259263 // brand new columns
260264 $ addedColumns = array_diff ($ currentColumns , $ oldColumns );
261265 foreach ($ addedColumns as $ columnName ) {
262- $ column = $ currentSchema -> getColumn ( $ columnName );
266+ $ column = $ this -> safeGetColumn ( $ currentSchema , $ columnName );
263267 /** @var int $key */
264268 $ key = array_search ($ columnName , $ currentColumns );
265269 if ($ key > 0 ) {
@@ -274,8 +278,8 @@ protected function getColumns(): void
274278
275279 // changes in columns meta-data
276280 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 );
279283 unset(
280284 $ column ['collate ' ],
281285 $ column ['fixed ' ],
@@ -351,7 +355,7 @@ protected function getColumns(): void
351355 $ removedColumns = array_diff ($ oldColumns , $ currentColumns );
352356 if ($ removedColumns ) {
353357 foreach ($ removedColumns as $ columnName ) {
354- $ column = $ this ->dumpSchema [$ table ]-> getColumn ( $ columnName );
358+ $ column = $ this ->safeGetColumn ( $ this -> dumpSchema [$ table ], $ columnName );
355359 /** @var int $key */
356360 $ key = array_search ($ columnName , $ oldColumns );
357361 if ($ key > 0 ) {
@@ -621,6 +625,67 @@ public function template(): string
621625 return 'Migrations.config/diff ' ;
622626 }
623627
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+
624689 /**
625690 * Gets the option parser instance and configures it.
626691 *
0 commit comments