@@ -257,26 +257,26 @@ private function processSubcommands(ReflectionClass $reflectionClass, string $cl
257257 $ args
258258 );
259259
260- // Then register private/protected methods with Command attributes as individual subcommands
260+ // Then register methods with Command attributes as individual subcommands
261261 foreach ($ reflectionClass ->getMethods () as $ method ) {
262262 $ commandAttributes = $ method ->getAttributes (Command::class);
263263
264264 if ($ commandAttributes === []) {
265265 continue ;
266266 }
267267
268- // Skip if method is public (already handled by base class registration)
269- if ($ method ->isPublic ()) {
270- continue ;
271- }
272-
273268 /** @var Command $commandAttribute */
274269 $ commandAttribute = $ commandAttributes [0 ]->newInstance ();
275270 $ subcommandName = $ commandAttribute ->getSubcommandName ($ method ->getName ());
276271 $ fullCommandName = "{$ baseCommandName } {$ subcommandName }" ;
277272
278273 // Create a callable array for the subcommand
279- $ callable = [$ className , $ method ->getName ()];
274+ // For private/protected methods, we need to use a wrapper with invade
275+ if ($ method ->isPrivate () || $ method ->isProtected ()) {
276+ $ callable = $ this ->createInvadeWrapper ($ className , $ method ->getName ());
277+ } else {
278+ $ callable = [$ className , $ method ->getName ()];
279+ }
280280
281281 // Register subcommand with WP CLI using method-level arguments
282282 if (\defined ('WP_CLI ' ) && WP_CLI ) {
@@ -329,6 +329,34 @@ private function collectMethodArguments(ReflectionMethod $reflectionMethod): arr
329329 return $ this ->collectWpCliArguments ($ reflectionMethod );
330330 }
331331
332+ /**
333+ * Create an invade wrapper for calling private/protected methods.
334+ *
335+ * @param string $className The class name
336+ * @param string $methodName The method name
337+ * @return array A callable array that uses invade
338+ */
339+ private function createInvadeWrapper (string $ className , string $ methodName ): array
340+ {
341+ return [new class ($ className , $ methodName ) {
342+ private string $ className ;
343+ private string $ methodName ;
344+
345+ public function __construct (string $ className , string $ methodName )
346+ {
347+ $ this ->className = $ className ;
348+ $ this ->methodName = $ methodName ;
349+ }
350+
351+ public function __invoke (array $ args , array $ assocArgs ): mixed
352+ {
353+ $ instance = app ($ this ->className );
354+ $ invadedInstance = invade ($ instance );
355+ return $ invadedInstance ->{$ this ->methodName }($ args , $ assocArgs );
356+ }
357+ }, '__invoke ' ];
358+ }
359+
332360 /**
333361 * Collect WP CLI arguments from reflection attributes.
334362 *
0 commit comments