diff --git a/src/Command/ControllerAllCommand.php b/src/Command/ControllerAllCommand.php index 43ffb3ca..c1bc2009 100644 --- a/src/Command/ControllerAllCommand.php +++ b/src/Command/ControllerAllCommand.php @@ -40,17 +40,6 @@ public static function defaultName(): string return 'bake controller all'; } - /** - * initialize - * - * @return void - */ - public function initialize(): void - { - parent::initialize(); - $this->controllerCommand = new ControllerCommand(); - } - /** * Execute the command. * @@ -82,6 +71,11 @@ public function execute(Arguments $args, ConsoleIo $io): ?int */ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { + // Assigned here (not initialize()) because on CakePHP 5.4+ the parser is built + // before initialize() runs, while older versions build it after. ??= keeps it + // safe under either ordering and idempotent across repeated calls. + $this->controllerCommand ??= new ControllerCommand(); + $parser = $this->controllerCommand->buildOptionParser($parser); $parser ->setDescription('Bake all controller files with tests.') diff --git a/src/Command/ModelAllCommand.php b/src/Command/ModelAllCommand.php index 2bbae1a7..8730e99d 100644 --- a/src/Command/ModelAllCommand.php +++ b/src/Command/ModelAllCommand.php @@ -40,17 +40,6 @@ public static function defaultName(): string return 'bake model all'; } - /** - * initialize - * - * @return void - */ - public function initialize(): void - { - parent::initialize(); - $this->modelCommand = new ModelCommand(); - } - /** * Gets the option parser instance and configures it. * @@ -59,6 +48,11 @@ public function initialize(): void */ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { + // Assigned here (not initialize()) because on CakePHP 5.4+ the parser is built + // before initialize() runs, while older versions build it after. ??= keeps it + // safe under either ordering and idempotent across repeated calls. + $this->modelCommand ??= new ModelCommand(); + $parser = $this->modelCommand->buildOptionParser($parser); $parser ->setDescription('Bake all model files with associations and validation.') diff --git a/src/Command/TemplateAllCommand.php b/src/Command/TemplateAllCommand.php index 0a1c72e5..2c7ff454 100644 --- a/src/Command/TemplateAllCommand.php +++ b/src/Command/TemplateAllCommand.php @@ -37,17 +37,6 @@ public static function defaultName(): string return 'bake template all'; } - /** - * initialize - * - * @return void - */ - public function initialize(): void - { - parent::initialize(); - $this->templateCommand = new TemplateCommand(); - } - /** * Execute the command. * @@ -85,6 +74,11 @@ public function execute(Arguments $args, ConsoleIo $io): int */ protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser { + // Assigned here (not initialize()) because on CakePHP 5.4+ the parser is built + // before initialize() runs, while older versions build it after. buildOptionParser() + // always runs before execute(), so this guarantees the subcommand is available there. + $this->templateCommand ??= new TemplateCommand(); + $parser = $this->_setCommonOptions($parser); $parser ->setDescription('Bake all view template files.') diff --git a/tests/TestCase/Command/ControllerAllCommandTest.php b/tests/TestCase/Command/ControllerAllCommandTest.php index 4b70b5d1..e5882a74 100644 --- a/tests/TestCase/Command/ControllerAllCommandTest.php +++ b/tests/TestCase/Command/ControllerAllCommandTest.php @@ -16,9 +16,11 @@ */ namespace Bake\Test\TestCase\Command; +use Bake\Command\ControllerAllCommand; use Bake\Test\App\Model\Table\BakeArticlesTable; use Bake\Test\TestCase\TestCase; use Cake\Console\CommandInterface; +use Cake\Console\ConsoleOptionParser; use Cake\Core\Plugin; use Cake\Utility\Inflector; @@ -91,4 +93,18 @@ public function testExecute(): void 'Test should not be created as options should be forwarded', ); } + + /** + * The option parser is built before initialize() runs, so the wrapped subcommand + * must already be available at that point. Regression test for the subcommand being + * assigned in initialize() instead of the constructor. + * + * @return void + */ + public function testGetOptionParserBeforeInitialize(): void + { + $command = new ControllerAllCommand(); + + $this->assertInstanceOf(ConsoleOptionParser::class, $command->getOptionParser()); + } } diff --git a/tests/TestCase/Command/ModelAllCommandTest.php b/tests/TestCase/Command/ModelAllCommandTest.php index 59bb054f..ae92a2db 100644 --- a/tests/TestCase/Command/ModelAllCommandTest.php +++ b/tests/TestCase/Command/ModelAllCommandTest.php @@ -16,9 +16,11 @@ */ namespace Bake\Test\TestCase\Command; +use Bake\Command\ModelAllCommand; use Bake\Test\TestCase\TestCase; use Bake\Utility\SubsetSchemaCollection; use Cake\Console\CommandInterface; +use Cake\Console\ConsoleOptionParser; use Cake\Datasource\ConnectionManager; use Cake\Utility\Inflector; @@ -99,4 +101,18 @@ public function testExecute(): void 'Table test should not be created as options should be forwarded', ); } + + /** + * The option parser is built before initialize() runs, so the wrapped subcommand + * must already be available at that point. Regression test for the subcommand being + * assigned in initialize() instead of the constructor. + * + * @return void + */ + public function testGetOptionParserBeforeInitialize(): void + { + $command = new ModelAllCommand(); + + $this->assertInstanceOf(ConsoleOptionParser::class, $command->getOptionParser()); + } } diff --git a/tests/TestCase/Command/TemplateAllCommandTest.php b/tests/TestCase/Command/TemplateAllCommandTest.php index c420da3f..1f2d8bc5 100644 --- a/tests/TestCase/Command/TemplateAllCommandTest.php +++ b/tests/TestCase/Command/TemplateAllCommandTest.php @@ -16,9 +16,11 @@ */ namespace Bake\Test\TestCase\Command; +use Bake\Command\TemplateAllCommand; use Bake\Test\TestCase\TestCase; use Bake\Utility\SubsetSchemaCollection; use Cake\Console\CommandInterface; +use Cake\Console\ConsoleOptionParser; use Cake\Core\Plugin; use Cake\Datasource\ConnectionManager; @@ -122,4 +124,18 @@ public function testExecuteOptionForwarding(): void $this->assertFileContains('title', $this->generatedFiles[0]); $this->assertFileNotContains('published', $this->generatedFiles[0]); } + + /** + * The option parser is built before initialize() runs, so the wrapped subcommand + * must already be available at that point. Regression test for the subcommand being + * assigned in initialize() instead of the constructor. + * + * @return void + */ + public function testGetOptionParserBeforeInitialize(): void + { + $command = new TemplateAllCommand(); + + $this->assertInstanceOf(ConsoleOptionParser::class, $command->getOptionParser()); + } }