@@ -13,6 +13,11 @@ class Shell_Command extends WP_CLI_Command {
1313 * is loaded, you have access to all the functions, classes and globals
1414 * that you can use within a WordPress plugin, for example.
1515 *
16+ * The `restart` command reloads the shell by spawning a new PHP process,
17+ * allowing modified code to be fully reloaded. Note that this requires
18+ * the `pcntl_exec()` function. If not available, the shell restarts
19+ * in-process, which resets variables but doesn't reload PHP files.
20+ *
1621 * ## OPTIONS
1722 *
1823 * [--basic]
@@ -33,7 +38,7 @@ class Shell_Command extends WP_CLI_Command {
3338 * # Restart the shell to reload code changes.
3439 * $ wp shell
3540 * wp> restart
36- * Restarting shell...
41+ * Restarting shell in new process ...
3742 * wp>
3843 *
3944 * # Watch a directory for changes and auto-restart.
@@ -88,6 +93,12 @@ public function __invoke( $_, $assoc_args ) {
8893 $ repl ->set_watch_path ( $ watch_path );
8994 }
9095 $ exit_code = $ repl ->start ();
96+
97+ // If restart requested, exec a new PHP process to reload all code
98+ if ( WP_CLI \Shell \REPL ::EXIT_CODE_RESTART === $ exit_code ) {
99+ $ this ->restart_process ( $ assoc_args );
100+ // If restart_process() returns, pcntl_exec is not available, continue in-process
101+ }
91102 } while ( WP_CLI \Shell \REPL ::EXIT_CODE_RESTART === $ exit_code );
92103 }
93104 }
@@ -110,4 +121,70 @@ private function resolve_watch_path( $path ) {
110121
111122 return $ realpath ;
112123 }
124+
125+ /**
126+ * Restart the shell by spawning a new PHP process.
127+ *
128+ * This replaces the current process with a new one to fully reload all code.
129+ * Falls back to in-process restart if pcntl_exec is not available.
130+ *
131+ * @param array $assoc_args Command arguments to preserve.
132+ */
133+ private function restart_process ( $ assoc_args ) {
134+ // Check if pcntl_exec is available
135+ if ( ! function_exists ( 'pcntl_exec ' ) ) {
136+ WP_CLI ::debug ( 'pcntl_exec not available, falling back to in-process restart ' , 'shell ' );
137+ return ;
138+ }
139+
140+ // Build the command to restart wp shell with the same arguments
141+ $ php_binary = Utils \get_php_binary ();
142+
143+ // Get the WP-CLI script path
144+ $ wp_cli_script = null ;
145+ foreach ( array ( 'argv ' , '_SERVER ' ) as $ source ) {
146+ if ( 'argv ' === $ source && isset ( $ GLOBALS ['argv ' ][0 ] ) ) {
147+ $ wp_cli_script = $ GLOBALS ['argv ' ][0 ];
148+ break ;
149+ } elseif ( '_SERVER ' === $ source && isset ( $ _SERVER ['argv ' ][0 ] ) ) {
150+ $ wp_cli_script = $ _SERVER ['argv ' ][0 ];
151+ break ;
152+ }
153+ }
154+
155+ if ( ! $ wp_cli_script ) {
156+ WP_CLI ::debug ( 'Could not determine WP-CLI script path, falling back to in-process restart ' , 'shell ' );
157+ return ;
158+ }
159+
160+ // Build arguments array
161+ $ args = array ( $ php_binary , $ wp_cli_script , 'shell ' );
162+
163+ if ( Utils \get_flag_value ( $ assoc_args , 'basic ' ) ) {
164+ $ args [] = '--basic ' ;
165+ }
166+
167+ $ watch_path = Utils \get_flag_value ( $ assoc_args , 'watch ' , false );
168+ if ( $ watch_path ) {
169+ $ args [] = '--watch= ' . $ watch_path ;
170+ }
171+
172+ // Add the path argument if present in $_SERVER
173+ if ( isset ( $ _SERVER ['argv ' ] ) ) {
174+ foreach ( $ _SERVER ['argv ' ] as $ arg ) {
175+ if ( '--path= ' === substr ( $ arg , 0 , 7 ) ) {
176+ $ args [] = $ arg ;
177+ }
178+ }
179+ }
180+
181+ WP_CLI ::log ( 'Restarting shell in new process... ' );
182+
183+ // Replace the current process with a new one
184+ // Note: pcntl_exec does not return on success
185+ pcntl_exec ( $ php_binary , array_slice ( $ args , 1 ) );
186+
187+ // If we reach here, exec failed
188+ WP_CLI ::warning ( 'Failed to restart process, falling back to in-process restart ' );
189+ }
113190}
0 commit comments