diff --git a/src/App/Traits/ConsoleAppTrait.php b/src/App/Traits/ConsoleAppTrait.php index 2cddd61c4..c32eb215a 100644 --- a/src/App/Traits/ConsoleAppTrait.php +++ b/src/App/Traits/ConsoleAppTrait.php @@ -15,9 +15,8 @@ namespace Quantum\App\Traits; use Symfony\Component\Console\Application; -use Quantum\Console\QtCommand; +use Quantum\Console\CommandDiscovery; use ReflectionException; -use ReflectionClass; use Exception; /** @@ -64,32 +63,18 @@ private function registerAppCommands() /** * @param string $directory * @param string $namespace - * @return void * @throws ReflectionException */ private function registerCommands(string $directory, string $namespace) { - foreach (get_directory_classes($directory) as $className) { - $commandClass = $namespace . $className; - - if (!class_exists($commandClass)) { - continue; - } - - $commandReflection = new ReflectionClass($commandClass); + $commands = CommandDiscovery::discover($directory, $namespace); - if (!$commandReflection->isInstantiable()) { - continue; - } - - if ($commandReflection->isSubclassOf(QtCommand::class)) { - $this->application->add($commandReflection->newInstance()); - } + foreach ($commands as $command) { + $this->application->add(new $command['class']()); } } /** - * @return void * @throws Exception */ private function validateCommand(): void diff --git a/src/Console/CommandDiscovery.php b/src/Console/CommandDiscovery.php new file mode 100644 index 000000000..2ea0fb886 --- /dev/null +++ b/src/Console/CommandDiscovery.php @@ -0,0 +1,62 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 2.9.9 + */ + +namespace Quantum\Console; + +use ReflectionException; +use ReflectionClass; + +/** + * Class CommandDiscovery + * @package Quantum\Console + */ +class CommandDiscovery +{ + + /** + * @param string $directory + * @param string $namespace + * @return array + * @throws ReflectionException + */ + public static function discover(string $directory, string $namespace): array + { + $commands = []; + + foreach (get_directory_classes($directory) as $className) { + $commandClass = $namespace . $className; + + if (!class_exists($commandClass)) { + continue; + } + + $commandReflection = new ReflectionClass($commandClass); + + if (!$commandReflection->isInstantiable() || !$commandReflection->isSubclassOf(QtCommand::class)) { + continue; + } + + $instance = $commandReflection->newInstance(); + + $commands[] = [ + 'class' => $commandClass, + 'name' => $instance->getName(), + 'description' => $instance->getDescription(), + 'help' => $instance->getHelp(), + ]; + } + + return $commands; + } +} \ No newline at end of file diff --git a/src/Console/Commands/EnvCommand.php b/src/Console/Commands/EnvCommand.php index 9f4fcba08..60aa305d7 100644 --- a/src/Console/Commands/EnvCommand.php +++ b/src/Console/Commands/EnvCommand.php @@ -9,14 +9,17 @@ * @author Arman Ag. * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) * @link http://quantum.softberg.org/ - * @since 2.9.7 + * @since 2.9.9 */ namespace Quantum\Console\Commands; use Quantum\Libraries\Storage\Factories\FileSystemFactory; +use Quantum\Config\Exceptions\ConfigException; use Quantum\App\Exceptions\BaseException; +use Quantum\Di\Exceptions\DiException; use Quantum\Console\QtCommand; +use ReflectionException; use Quantum\App\App; /** @@ -55,6 +58,9 @@ class EnvCommand extends QtCommand /** * Executes the command and creates new .env file * @throws BaseException + * @throws ConfigException + * @throws DiException + * @throws ReflectionException */ public function exec() { diff --git a/src/Console/Commands/InstallToolkitCommand.php b/src/Console/Commands/InstallToolkitCommand.php index d1d61a32c..78ef4593e 100644 --- a/src/Console/Commands/InstallToolkitCommand.php +++ b/src/Console/Commands/InstallToolkitCommand.php @@ -9,13 +9,13 @@ * @author Arman Ag. * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) * @link http://quantum.softberg.org/ - * @since 2.9.8 + * @since 2.9.9 */ namespace Quantum\Console\Commands; -use Quantum\Environment\Exceptions\EnvException; use Symfony\Component\Console\Exception\ExceptionInterface; +use Quantum\Environment\Exceptions\EnvException; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Input\ArrayInput; use Quantum\Environment\Environment; diff --git a/src/Console/QtCommand.php b/src/Console/QtCommand.php index 5e1ca59dc..e3277fb1d 100644 --- a/src/Console/QtCommand.php +++ b/src/Console/QtCommand.php @@ -9,7 +9,7 @@ * @author Arman Ag. * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) * @link http://quantum.softberg.org/ - * @since 2.8.0 + * @since 2.9.9 */ namespace Quantum\Console; @@ -83,33 +83,12 @@ public function __construct() $this->setHelp($this->help); } - /** - * Configures the current command. - */ - protected function configure() - { - $this->setArguments(); - $this->setOptions(); - } - - /** - * Executes the current command. - * @param InputInterface $input - * @param OutputInterface $output - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->input = $input; - $this->output = $output; - $this->exec(); - } - /** * Returns the argument value for a given argument name. * @param string|null $key * @return mixed|string */ - protected function getArgument(string $key = null) + public function getArgument(string $key = null) { return $this->input->getArgument($key) ?? ''; } @@ -119,7 +98,7 @@ protected function getArgument(string $key = null) * @param string|null $key * @return mixed|string */ - protected function getOption(string $key = null) + public function getOption(string $key = null) { return $this->input->getOption($key) ?? ''; } @@ -137,7 +116,7 @@ public function output(string $message) * Outputs the string to console as info * @param string $message */ - protected function info(string $message) + public function info(string $message) { $this->output->writeln("$message"); } @@ -146,7 +125,7 @@ protected function info(string $message) * Outputs the string to console as comment * @param string $message */ - protected function comment(string $message) + public function comment(string $message) { $this->output->writeln("$message"); } @@ -155,7 +134,7 @@ protected function comment(string $message) * Outputs the string to console as question * @param string $message */ - protected function question(string $message) + public function question(string $message) { $this->output->writeln("$message"); } @@ -168,24 +147,42 @@ protected function question(string $message) public function confirm(string $message): bool { $helper = $this->getHelper('question'); - $question = new ConfirmationQuestion($message . ' [y/N] ', false); - if ($helper->ask($this->input, $this->output, $question)) { - return true; - } + $question = new ConfirmationQuestion($message . ' [y/N] ', false); - return false; + return (bool) $helper->ask($this->input, $this->output, $question); } /** * Outputs the string to console as error * @param string $message */ - protected function error(string $message) + public function error(string $message) { $this->output->writeln("$message"); } + /** + * Configures the current command. + */ + protected function configure() + { + $this->setArguments(); + $this->setOptions(); + } + + /** + * Executes the current command. + * @param InputInterface $input + * @param OutputInterface $output + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + $this->exec(); + } + /** * Sets command arguments */ @@ -228,5 +225,4 @@ private function setOptions() } } } - } \ No newline at end of file diff --git a/src/Module/Templates/DemoWeb/src/Controllers/PageController.php.tpl b/src/Module/Templates/DemoWeb/src/Controllers/PageController.php.tpl index 51a622511..378601c57 100644 --- a/src/Module/Templates/DemoWeb/src/Controllers/PageController.php.tpl +++ b/src/Module/Templates/DemoWeb/src/Controllers/PageController.php.tpl @@ -14,6 +14,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers; +use {{MODULE_NAMESPACE}}\Services\CommandService; use Quantum\Http\Response; /** @@ -51,6 +52,8 @@ class PageController extends BaseController 'title' => t('common.about') . ' | ' . config()->get('app.name'), ]); - $response->html($this->view->render('pages/about')); + $commands = service(CommandService::class)->getAllCommands(); + + $response->html($this->view->render('pages/about', ['commands' => $commands])); } } \ No newline at end of file diff --git a/src/Module/Templates/DemoWeb/src/Services/CommandService.php.tpl b/src/Module/Templates/DemoWeb/src/Services/CommandService.php.tpl new file mode 100644 index 000000000..4f14f7019 --- /dev/null +++ b/src/Module/Templates/DemoWeb/src/Services/CommandService.php.tpl @@ -0,0 +1,47 @@ + + * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org) + * @link http://quantum.softberg.org/ + * @since 2.9.9 + */ + + +namespace {{MODULE_NAMESPACE}}\Services; + +use Quantum\Console\CommandDiscovery; +use Quantum\Service\QtService; + +/** + * Class CommandService + * @package Modules\{{MODULE_NAME}} + */ +class CommandService extends QtService +{ + + /** + * Get all available commands (core + app) + * @return array + * @throws ReflectionException + */ + public function getAllCommands(): array + { + $coreCommands = CommandDiscovery::discover( + framework_dir() . DS . 'Console' . DS . 'Commands', + '\\Quantum\\Console\\Commands\\' + ); + + $appCommands = CommandDiscovery::discover( + base_dir() . DS . 'shared' . DS . 'Commands', + '\\Shared\\Commands\\' + ); + + return array_merge($coreCommands, $appCommands); + } +} \ No newline at end of file diff --git a/src/Module/Templates/DemoWeb/src/Views/pages/about.php.tpl b/src/Module/Templates/DemoWeb/src/Views/pages/about.php.tpl index 9d3111297..42a347b44 100644 --- a/src/Module/Templates/DemoWeb/src/Views/pages/about.php.tpl +++ b/src/Module/Templates/DemoWeb/src/Views/pages/about.php.tpl @@ -9,30 +9,18 @@

-

Quantum CLI Commands

+

- - - - +
- +
- - diff --git a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php index df8178ac3..1adf89dc2 100644 --- a/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php +++ b/tests/Unit/App/Adapters/ConsoleAppAdapterTest.php @@ -2,6 +2,8 @@ namespace Quantum\Tests\Unit\App\Adapters; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputInterface; use Quantum\App\Adapters\ConsoleAppAdapter; use Symfony\Component\Console\Application; use PHPUnit\Framework\TestCase; @@ -22,8 +24,13 @@ public function setUp(): void $applicationMock->shouldReceive('getName')->andReturn('Qt Console Application'); $applicationMock->shouldReceive('run')->andReturn(0); - $this->consoleAppAdapter = Mockery::mock(ConsoleAppAdapter::class)->makePartial(); - $this->consoleAppAdapter->shouldReceive('createApplication')->andReturn($applicationMock); + $this->consoleAppAdapter = Mockery::mock(ConsoleAppAdapter::class) + ->shouldAllowMockingProtectedMethods() + ->makePartial(); + + $this->consoleAppAdapter + ->shouldReceive('createApplication') + ->andReturn($applicationMock); } public function tearDown(): void diff --git a/tests/Unit/Console/CommandDiscoveryTest.php b/tests/Unit/Console/CommandDiscoveryTest.php new file mode 100644 index 000000000..d171b1a10 --- /dev/null +++ b/tests/Unit/Console/CommandDiscoveryTest.php @@ -0,0 +1,44 @@ +assertIsArray($commands, 'Discover should return an array'); + + $this->assertNotEmpty($commands, 'No core commands were discovered'); + + $command = $commands[0]; + + $this->assertArrayHasKey('class', $command); + + $this->assertArrayHasKey('name', $command); + + $this->assertArrayHasKey('description', $command); + + $this->assertArrayHasKey('help', $command); + + $this->assertTrue(class_exists($command['class'])); + + $instance = new $command['class'](); + + $this->assertInstanceOf(QtCommand::class, $instance); + + $this->assertNotEmpty($instance->getName()); + } +} \ No newline at end of file diff --git a/tests/Unit/Console/QtCommandTest.php b/tests/Unit/Console/QtCommandTest.php new file mode 100644 index 000000000..b5098a195 --- /dev/null +++ b/tests/Unit/Console/QtCommandTest.php @@ -0,0 +1,101 @@ +command = new TestCommand(); + + $helper = new QuestionHelper(); + $this->command->setHelperSet(new HelperSet(['question' => $helper])); + + $this->tester = new CommandTester($this->command); + } + + public function testQtCommandMetadataIsConfigured() + { + $this->assertSame('test:dummy', $this->command->getName()); + + $this->assertSame('Dummy test command', $this->command->getDescription()); + + $this->assertSame('Used only for core command discovery tests', $this->command->getHelp()); + } + + public function testQtCommandArgumentsAndOptionsAreRegistered() + { + $definition = $this->command->getDefinition(); + + $this->assertTrue($definition->hasArgument('uuid')); + + $this->assertTrue($definition->hasOption('force')); + } + + public function testGetArgumentAndOption() + { + $this->tester->execute([ + 'uuid' => '12345', + '--force' => true + ]); + + $this->assertEquals('12345', $this->command->getArgument('uuid')); + + $this->assertTrue($this->command->getOption('force')); + } + + public function testOutputMethods(): void + { + $this->tester->execute([]); + + $this->command->output('plain message'); + + $this->command->info('info message'); + + $this->command->comment('comment message'); + + $this->command->error('error message'); + + $display = $this->tester->getDisplay(); + + $this->assertStringContainsString("plain message", $display); + + $this->assertStringContainsString("info message", $display); + + $this->assertStringContainsString("comment message", $display); + + $this->assertStringContainsString("error message", $display); + } + + public function testQuestionOutput(): void + { + $this->tester->execute([]); + + $this->command->question('are you sure?'); + + $display = $this->tester->getDisplay(); + + $this->assertStringContainsString('are you sure?', $display); + } + + public function testConfirmReturnsTrue(): void + { + $this->tester->setInputs(['y']); + $this->tester->execute([]); + + $this->assertTrue($this->command->confirm('Confirm action')); + } +} \ No newline at end of file diff --git a/tests/_root/shared/Commands/TestCommand.php b/tests/_root/shared/Commands/TestCommand.php new file mode 100644 index 000000000..aa5920f36 --- /dev/null +++ b/tests/_root/shared/Commands/TestCommand.php @@ -0,0 +1,25 @@ +