3131 */
3232class MysqlAdapter extends AbstractAdapter
3333{
34+ /**
35+ * Maximum length for identifiers (table names, column names, constraint names, etc.)
36+ */
37+ protected const IDENTIFIER_MAX_LENGTH = 64 ;
38+
3439 /**
3540 * @var string[]
3641 */
@@ -371,8 +376,10 @@ public function createTable(TableMetadata $table, array $columns = [], array $in
371376 protected function mapColumnData (array $ data ): array
372377 {
373378 if ($ data ['type ' ] == self ::TYPE_TEXT && $ data ['length ' ] !== null ) {
379+ // Accept both migrations TEXT_LONG and CakePHP LENGTH_LONG for backward compatibility
380+ // with migrations generated before the fix (LENGTH_TINY/MEDIUM are already equal to TEXT_TINY/MEDIUM)
374381 $ data ['length ' ] = match ($ data ['length ' ]) {
375- self ::TEXT_LONG => TableSchema::LENGTH_LONG ,
382+ self ::TEXT_LONG , TableSchema:: LENGTH_LONG => TableSchema::LENGTH_LONG ,
376383 self ::TEXT_MEDIUM => TableSchema::LENGTH_MEDIUM ,
377384 self ::TEXT_REGULAR => null ,
378385 self ::TEXT_TINY => TableSchema::LENGTH_TINY ,
@@ -979,7 +986,7 @@ protected function getAddForeignKeyInstructions(TableMetadata $table, ForeignKey
979986 {
980987 $ alter = sprintf (
981988 'ADD %s ' ,
982- $ this ->getForeignKeySqlDefinition ($ foreignKey ),
989+ $ this ->getForeignKeySqlDefinition ($ foreignKey, $ table -> getName () ),
983990 );
984991
985992 return new AlterInstructions ([$ alter ]);
@@ -1194,15 +1201,13 @@ protected function getIndexSqlDefinition(Index $index): string
11941201 * Gets the MySQL Foreign Key Definition for an ForeignKey object.
11951202 *
11961203 * @param \Migrations\Db\Table\ForeignKey $foreignKey Foreign key
1204+ * @param string $tableName Table name for auto-generating constraint name
11971205 * @return string
11981206 */
1199- protected function getForeignKeySqlDefinition (ForeignKey $ foreignKey ): string
1207+ protected function getForeignKeySqlDefinition (ForeignKey $ foreignKey, string $ tableName ): string
12001208 {
1201- $ def = '' ;
1202- $ name = $ foreignKey ->getName ();
1203- if ($ name ) {
1204- $ def .= ' CONSTRAINT ' . $ this ->quoteColumnName ($ name );
1205- }
1209+ $ constraintName = $ foreignKey ->getName () ?: $ this ->getUniqueForeignKeyName ($ tableName , $ foreignKey ->getColumns ());
1210+ $ def = ' CONSTRAINT ' . $ this ->quoteColumnName ($ constraintName );
12061211 $ columnNames = [];
12071212 foreach ($ foreignKey ->getColumns () as $ column ) {
12081213 $ columnNames [] = $ this ->quoteColumnName ($ column );
@@ -1229,6 +1234,35 @@ protected function getForeignKeySqlDefinition(ForeignKey $foreignKey): string
12291234 return $ def ;
12301235 }
12311236
1237+ /**
1238+ * Generate a unique foreign key constraint name.
1239+ *
1240+ * @param string $tableName Table name
1241+ * @param array<string> $columns Column names
1242+ * @return string
1243+ */
1244+ protected function getUniqueForeignKeyName (string $ tableName , array $ columns ): string
1245+ {
1246+ $ baseName = $ tableName . '_ ' . implode ('_ ' , $ columns );
1247+ $ maxLength = static ::IDENTIFIER_MAX_LENGTH - 3 ;
1248+ if (strlen ($ baseName ) > $ maxLength ) {
1249+ $ baseName = substr ($ baseName , 0 , $ maxLength );
1250+ }
1251+ $ existingKeys = $ this ->getForeignKeys ($ tableName );
1252+ $ existingNames = array_column ($ existingKeys , 'name ' );
1253+
1254+ if (!in_array ($ baseName , $ existingNames , true )) {
1255+ return $ baseName ;
1256+ }
1257+
1258+ $ counter = 2 ;
1259+ while (in_array ($ baseName . '_ ' . $ counter , $ existingNames , true )) {
1260+ $ counter ++;
1261+ }
1262+
1263+ return $ baseName . '_ ' . $ counter ;
1264+ }
1265+
12321266 /**
12331267 * Returns MySQL column types (inherited and MySQL specified).
12341268 *
0 commit comments