@@ -53,17 +53,31 @@ protected function configure(): void
5353 InputOption::VALUE_NONE ,
5454 'Alias of --drop-c-item-property (drops legacy table c_item_property after successful migration, only if no pending attendances remain). '
5555 );
56+
57+ $ this ->addOption (
58+ 'dry-run ' ,
59+ null ,
60+ InputOption::VALUE_NONE ,
61+ 'Preview mode: prints the computed resource_node.path and rolls back the transaction (no data persisted). '
62+ );
5663 }
5764
5865 protected function execute (InputInterface $ input , OutputInterface $ output ): int
5966 {
6067 $ io = new SymfonyStyle ($ input , $ output );
6168 $ io ->title ('Fast attendances migration ' );
6269
70+ $ dryRun = (bool ) $ input ->getOption ('dry-run ' );
71+
6372 // Accept both flags as the same action (alias)
6473 $ dropItemProperty = (bool ) $ input ->getOption ('drop-c-item-property ' )
6574 || (bool ) $ input ->getOption ('drop-c-item-properties ' );
6675
76+ if ($ dryRun && $ dropItemProperty ) {
77+ $ io ->note ('Dry-run enabled: ignoring --drop-c-item-property (no schema changes will be applied). ' );
78+ $ dropItemProperty = false ;
79+ }
80+
6781 $ fallbackAdminId = $ this ->getFallbackAdminId ();
6882 $ uuidIsBinary = $ this ->detectUuidIsBinary ();
6983
@@ -95,6 +109,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
95109 return Command::SUCCESS ;
96110 }
97111
112+ if ($ dryRun ) {
113+ $ io ->warning ('Dry-run enabled: all changes will be rolled back. Only paths will be printed. ' );
114+ }
115+
98116 $ processedCourses = 0 ;
99117 $ processedAttendances = 0 ;
100118
@@ -142,7 +160,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
142160 continue ;
143161 }
144162
145- $ io ->section ("Course {$ courseId }: migrating " .\count ($ attendanceRows ).' attendances ' );
163+ $ io ->section ("Course {$ courseId }: processing " .\count ($ attendanceRows ).' attendances ' );
146164
147165 $ displayOrder = 0 ;
148166 $ this ->connection ->beginTransaction ();
@@ -218,8 +236,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
218236 'user_id ' => $ toUserId ,
219237 ]);
220238
221- // resource_node.path should be aligned with the course tree: <coursePath>-<nodeId>/
222- $ newPath = $ coursePath .'- ' .$ resourceNodeId .'/ ' ;
239+ // resource_node.path should follow the same structure as the standard migration:
240+ // <parentPath>/<title>-<attendanceIid>-<nodeId>/
241+ $ segmentTitle = trim (str_replace (['/ ' , '\\' ], '- ' , $ attendanceTitle ));
242+ $ segmentTitle = preg_replace ('/\s+/ ' , ' ' , $ segmentTitle ) ?: $ segmentTitle ;
243+
244+ $ newPath = $ coursePath .'/ ' .$ segmentTitle .'- ' .$ attendanceId .'- ' .$ resourceNodeId .'/ ' ;
245+
246+ if ($ dryRun ) {
247+ $ io ->writeln (sprintf (' - Attendance %d -> path: %s ' , $ attendanceId , $ newPath ));
248+ }
249+
223250 $ this ->connection ->update ('resource_node ' , ['path ' => $ newPath ], ['id ' => $ resourceNodeId ]);
224251
225252 // Mark attendance as migrated by storing the new resource_node_id.
@@ -229,15 +256,31 @@ protected function execute(InputInterface $input, OutputInterface $output): int
229256 $ processedAttendances ++;
230257 }
231258
232- $ this ->connection ->commit ();
259+ if ($ dryRun ) {
260+ $ this ->connection ->rollBack ();
261+ $ io ->note ("Dry-run: rolled back changes for course {$ courseId }. " );
262+ } else {
263+ $ this ->connection ->commit ();
264+ }
265+
233266 $ processedCourses ++;
234267 } catch (\Throwable $ e ) {
235- $ this ->connection ->rollBack ();
268+ try {
269+ $ this ->connection ->rollBack ();
270+ } catch (\Throwable ) {
271+ // Ignore rollback failures.
272+ }
273+
236274 $ io ->error ("Course {$ courseId }: transaction failed - " .$ e ->getMessage ());
237275 return Command::FAILURE ;
238276 }
239277 }
240278
279+ if ($ dryRun ) {
280+ $ io ->success ("Dry-run finished. Courses processed: {$ processedCourses }. Attendances simulated: {$ processedAttendances }. " );
281+ return Command::SUCCESS ;
282+ }
283+
241284 $ io ->success ("Done. Courses processed: {$ processedCourses }. Attendances migrated: {$ processedAttendances }. " );
242285
243286 if ($ dropItemProperty ) {
@@ -353,14 +396,12 @@ private function maybeDropItemProperty(SymfonyStyle $io): void
353396 $ io ->section ('Dropping legacy table "c_item_property"... ' );
354397
355398 try {
356- // DBAL generates the proper DROP TABLE for the current platform.
357399 $ sm = $ this ->connection ->createSchemaManager ();
358400 $ sm ->dropTable ('c_item_property ' );
359401
360402 $ io ->success ('Legacy table "c_item_property" dropped. ' );
361403 } catch (DbalException $ e ) {
362404 $ io ->error ('Failed to drop legacy table "c_item_property": ' .$ e ->getMessage ());
363- // Optional: throw $e; // if you want to fail hard
364405 }
365406 }
366407
0 commit comments