88use Doctrine \DBAL \Platforms \AbstractPlatform ;
99use Doctrine \DBAL \Schema \AbstractAsset ;
1010use Doctrine \DBAL \Schema \AbstractSchemaManager ;
11+ use Doctrine \DBAL \Schema \ColumnEditor ;
1112use Doctrine \DBAL \Schema \ComparatorConfig ;
1213use Doctrine \DBAL \Schema \DefaultExpression ;
1314use Doctrine \DBAL \Schema \DefaultExpression \CurrentDate ;
2122use Doctrine \DBAL \Schema \NamedObject ;
2223use Doctrine \DBAL \Schema \PrimaryKeyConstraint ;
2324use Doctrine \DBAL \Schema \Schema ;
25+ use Doctrine \DBAL \Schema \SchemaConfig ;
2426use Doctrine \DBAL \Schema \Table ;
2527use Doctrine \DBAL \Types \Types ;
2628use Doctrine \Deprecations \Deprecation ;
@@ -204,11 +206,27 @@ public function getSchemaFromMetadata(array $classes): Schema
204206 continue ;
205207 }
206208
207- $ table = $ schema ->createTable ($ this ->quoteStrategy ->getTableName ($ class , $ this ->platform ));
209+ // Create table object
210+ // @phpstan-ignore function.impossibleType (Using unreleased Schema::edit() API)
211+ if (method_exists (Schema::class, 'edit ' )) {
212+ $ table = new Table (
213+ name: $ this ->quoteStrategy ->getTableName ($ class , $ this ->platform ),
214+ configuration: $ metadataSchemaConfig ->toTableConfiguration (),
215+ );
216+ // Add default table options (charset, collation, engine, etc.)
217+ foreach ($ metadataSchemaConfig ->getDefaultTableOptions () as $ option => $ value ) {
218+ $ table ->addOption ($ option , $ value );
219+ }
220+ } else {
221+ $ table = $ schema ->createTable ($ this ->quoteStrategy ->getTableName ($ class , $ this ->platform ));
222+ }
208223
209224 if ($ class ->isInheritanceTypeSingleTable ()) {
225+ // For new schema API: collect join tables to add after this entity table
226+ $ joinTablesToAdd = [];
227+
210228 $ this ->gatherColumns ($ class , $ table );
211- $ this ->gatherRelationsSql ($ class , $ table , $ schema , $ addedFks , $ blacklistedFks );
229+ $ this ->gatherRelationsSql ($ class , $ table , $ schema , $ addedFks , $ blacklistedFks, $ metadataSchemaConfig , $ joinTablesToAdd );
212230
213231 // Add the discriminator column
214232 $ this ->addDiscriminatorColumnDefinition ($ class , $ table );
@@ -222,18 +240,37 @@ public function getSchemaFromMetadata(array $classes): Schema
222240 foreach ($ class ->subClasses as $ subClassName ) {
223241 $ subClass = $ this ->em ->getClassMetadata ($ subClassName );
224242 $ this ->gatherColumns ($ subClass , $ table );
225- $ this ->gatherRelationsSql ($ subClass , $ table , $ schema , $ addedFks , $ blacklistedFks );
243+ $ this ->gatherRelationsSql (
244+ $ subClass ,
245+ $ table ,
246+ $ schema ,
247+ $ addedFks ,
248+ $ blacklistedFks ,
249+ $ metadataSchemaConfig ,
250+ $ joinTablesToAdd ,
251+ );
226252 $ processedClasses [$ subClassName ] = true ;
227253 }
228254 } elseif ($ class ->isInheritanceTypeJoined ()) {
255+ // For new schema API: collect join tables to add after this entity table
256+ $ joinTablesToAdd = [];
257+
229258 // Add all non-inherited fields as columns
230259 foreach ($ class ->fieldMappings as $ fieldName => $ mapping ) {
231260 if (! isset ($ mapping ->inherited )) {
232261 $ this ->gatherColumn ($ class , $ mapping , $ table );
233262 }
234263 }
235264
236- $ this ->gatherRelationsSql ($ class , $ table , $ schema , $ addedFks , $ blacklistedFks );
265+ $ this ->gatherRelationsSql (
266+ $ class ,
267+ $ table ,
268+ $ schema ,
269+ $ addedFks ,
270+ $ blacklistedFks ,
271+ $ metadataSchemaConfig ,
272+ $ joinTablesToAdd ,
273+ );
237274
238275 // Add the discriminator column only to the root table
239276 if ($ class ->name === $ class ->rootEntityName ) {
@@ -253,7 +290,18 @@ public function getSchemaFromMetadata(array $classes): Schema
253290 $ this ->platform ,
254291 );
255292 // TODO: This seems rather hackish, can we optimize it?
256- $ table ->getColumn ($ columnName )->setAutoincrement (false );
293+ // Use new column editor API only when new schema editor API is available (DBAL 4.5+)
294+ // @phpstan-ignore function.impossibleType (Using unreleased Schema::edit() API for version detection)
295+ if (method_exists (Schema::class, 'edit ' )) {
296+ // New API: modify column using table editor (creates new table object)
297+ // This is safe because we'll add the table to schema later after all modifications
298+ $ table = $ table ->edit ()->modifyColumnByUnquotedName (
299+ $ columnName ,
300+ static fn (ColumnEditor $ column ) => $ column ->setAutoincrement (false ),
301+ )->create ();
302+ } else {
303+ $ table ->getColumn ($ columnName )->setAutoincrement (false );
304+ }
257305
258306 $ pkColumns [] = $ columnName ;
259307 $ inheritedKeyColumns [] = $ columnName ;
@@ -305,8 +353,19 @@ public function getSchemaFromMetadata(array $classes): Schema
305353 }
306354 }
307355 } else {
356+ // For new schema API: collect join tables to add after this entity table
357+ $ joinTablesToAdd = [];
358+
308359 $ this ->gatherColumns ($ class , $ table );
309- $ this ->gatherRelationsSql ($ class , $ table , $ schema , $ addedFks , $ blacklistedFks );
360+ $ this ->gatherRelationsSql (
361+ $ class ,
362+ $ table ,
363+ $ schema ,
364+ $ addedFks ,
365+ $ blacklistedFks ,
366+ $ metadataSchemaConfig ,
367+ $ joinTablesToAdd ,
368+ );
310369 }
311370
312371 $ pkColumns = [];
@@ -377,6 +436,23 @@ public function getSchemaFromMetadata(array $classes): Schema
377436
378437 $ processedClasses [$ class ->name ] = true ;
379438
439+ // Add the fully populated table to the schema
440+ // @phpstan-ignore function.impossibleType (Using unreleased Schema::edit() API)
441+ if (method_exists (Schema::class, 'edit ' )) {
442+ // @phpstan-ignore method.notFound (Using unreleased Schema::edit() API)
443+ $ schemaEditor = $ schema ->edit ();
444+ $ schemaEditor ->addTable ($ table );
445+ $ schema = $ schemaEditor ->create ();
446+
447+ // Add any join tables collected during relation processing
448+ // This ensures join tables appear right after their owning entity table
449+ foreach ($ joinTablesToAdd as $ joinTable ) {
450+ $ schemaEditor = $ schema ->edit ();
451+ $ schemaEditor ->addTable ($ joinTable );
452+ $ schema = $ schemaEditor ->create ();
453+ }
454+ }
455+
380456 if ($ class ->isIdGeneratorSequence () && $ class ->name === $ class ->rootEntityName ) {
381457 $ seqDef = $ class ->sequenceGeneratorDefinition ;
382458 $ quotedName = $ this ->quoteStrategy ->getSequenceName ($ seqDef , $ class , $ this ->platform );
@@ -614,15 +690,18 @@ private function gatherColumn(
614690 * fkOptions: array{onDelete?: string, deferrable?: bool, deferred?: bool}
615691 * }> $addedFks
616692 * @phpstan-param array<string, bool> $blacklistedFks
693+ * @phpstan-param list<Table> $joinTablesToAdd
617694 *
618695 * @throws NotSupported
619696 */
620697 private function gatherRelationsSql (
621698 ClassMetadata $ class ,
622699 Table $ table ,
623- Schema $ schema ,
700+ Schema & $ schema ,
624701 array &$ addedFks ,
625702 array &$ blacklistedFks ,
703+ SchemaConfig $ schemaConfig ,
704+ array &$ joinTablesToAdd ,
626705 ): void {
627706 foreach ($ class ->associationMappings as $ id => $ mapping ) {
628707 if (isset ($ mapping ->inherited ) && ! in_array ($ id , $ class ->identifier , true )) {
@@ -647,12 +726,25 @@ private function gatherRelationsSql(
647726 // create join table
648727 $ joinTable = $ mapping ->joinTable ;
649728
650- $ theJoinTable = $ schema ->createTable (
651- $ this ->quoteStrategy ->getJoinTableName ($ mapping , $ foreignClass , $ this ->platform ),
652- );
729+ $ tableName = $ this ->quoteStrategy ->getJoinTableName ($ mapping , $ foreignClass , $ this ->platform );
653730
654- foreach ($ joinTable ->options as $ key => $ val ) {
655- $ theJoinTable ->addOption ($ key , $ val );
731+ // Create the join table object
732+ // @phpstan-ignore function.impossibleType (Using unreleased Schema::edit() API)
733+ if (method_exists (Schema::class, 'edit ' )) {
734+ $ theJoinTable = new Table (
735+ name: $ tableName ,
736+ options: $ joinTable ->options ,
737+ configuration: $ schemaConfig ->toTableConfiguration (),
738+ );
739+ // Add default table options (charset, collation, engine, etc.)
740+ foreach ($ schemaConfig ->getDefaultTableOptions () as $ option => $ value ) {
741+ $ theJoinTable ->addOption ($ option , $ value );
742+ }
743+ } else {
744+ $ theJoinTable = $ schema ->createTable ($ tableName );
745+ foreach ($ joinTable ->options as $ key => $ val ) {
746+ $ theJoinTable ->addOption ($ key , $ val );
747+ }
656748 }
657749
658750 $ primaryKeyColumns = [];
@@ -680,6 +772,13 @@ private function gatherRelationsSql(
680772 );
681773
682774 self ::addPrimaryKeyConstraint ($ theJoinTable , $ primaryKeyColumns );
775+
776+ // For new schema API: collect join table for deferred addition at end
777+ // For old schema API: table already added via createTable() above
778+ // @phpstan-ignore function.impossibleType (Using unreleased Schema::edit() API)
779+ if (method_exists (Schema::class, 'edit ' )) {
780+ $ joinTablesToAdd [] = $ theJoinTable ;
781+ }
683782 }
684783 }
685784 }
0 commit comments