You've already forked Php-Template
feat: refactor command structure to use attribute-based command registration and enhance input/output handling (#28)
All checks were successful
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 1m57s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 3m16s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 3m11s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Successful in 3m13s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Successful in 3m32s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 53s
All checks were successful
🧪✨ Tests Workflow / 🛡️ 🔒 Library Audit (push) Successful in 1m57s
🧪✨ Tests Workflow / 🧪 ✨ Database Migrations (push) Successful in 3m16s
🧪✨ Tests Workflow / 📝 ✨ Code Lint (push) Successful in 3m11s
🧪✨ Tests Workflow / 🐙 🔍 Code Sniffer (push) Successful in 3m13s
🧪✨ Tests Workflow / 🧪 ✅ Unit Tests (push) Successful in 3m32s
🧪✨ Tests Workflow / 🛡️ 🔒 License Check (push) Successful in 53s
Reviewed-on: #28 Co-authored-by: Ron Rise <ron@siteworxpro.com> Co-committed-by: Ron Rise <ron@siteworxpro.com>
This commit was merged in pull request #28.
This commit is contained in:
@@ -20,7 +20,6 @@
|
||||
"predis/predis": "^v3.2.0",
|
||||
"siteworxpro/http-status": "0.0.2",
|
||||
"lcobucci/jwt": "^5.6",
|
||||
"adhocore/cli": "^1.9",
|
||||
"robinvdvleuten/ulid": "^5.0",
|
||||
"monolog/monolog": "^3.9",
|
||||
"react/promise": "^3",
|
||||
@@ -28,7 +27,9 @@
|
||||
"guzzlehttp/guzzle": "^7.10",
|
||||
"zircote/swagger-php": "^5.7",
|
||||
"spiral/roadrunner-grpc": "^3.5",
|
||||
"league/tactician": "^1.1"
|
||||
"league/tactician": "^1.1",
|
||||
"symfony/console": "^v7.4.3",
|
||||
"league/climate": "^3.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.4",
|
||||
@@ -46,7 +47,7 @@
|
||||
"composer run-script tests:phpstan"
|
||||
],
|
||||
"tests:unit": [
|
||||
"phpunit --colors=always --display-deprecations tests"
|
||||
"phpunit --testdox --colors=always --display-notices --display-deprecations tests"
|
||||
],
|
||||
"tests:unit:coverage": [
|
||||
"phpunit --coverage-text --colors=never --display-deprecations --log-junit tests/reports/junit.xml --coverage-html tests/reports/html tests "
|
||||
|
||||
1599
composer.lock
generated
1599
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli;
|
||||
|
||||
use Ahc\Cli\Application;
|
||||
use Siteworxpro\App\Cli\Commands\DemoCommand;
|
||||
use Siteworxpro\App\Cli\Commands\Queue\Start;
|
||||
use Siteworxpro\App\Cli\Commands\Queue\TestJob;
|
||||
use Siteworxpro\App\Helpers\Version;
|
||||
use Siteworxpro\App\Kernel;
|
||||
use Siteworxpro\App\Services\Facades\Config;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
class App
|
||||
{
|
||||
@@ -22,25 +22,28 @@ class App
|
||||
public function __construct()
|
||||
{
|
||||
Kernel::boot();
|
||||
$this->app = new Application('Php-Template', Version::VERSION);
|
||||
|
||||
$this->app->add(new DemoCommand());
|
||||
$this->app->add(new Start());
|
||||
$this->app->add(new TestJob());
|
||||
$this->app = new Application('Siteworxpro Auth', Version::VERSION);
|
||||
|
||||
$this->app->setCatchErrors();
|
||||
|
||||
$this->app->addCommand(new DemoCommand());
|
||||
$this->app->addCommand(new Start());
|
||||
$this->app->addCommand(new TestJob());
|
||||
}
|
||||
|
||||
public function run(): int
|
||||
{
|
||||
$this->app->logo(
|
||||
<<<EOF
|
||||
▄▄▄▄▄ ▄ ▄ ▄▄▄▄▄ ▄▄▄▄▄▄▄ ▀▀█ ▄
|
||||
█ ▀█ █ █ █ ▀█ █ ▄▄▄ ▄▄▄▄▄ ▄▄▄▄ █ ▄▄▄ ▄▄█▄▄ ▄▄▄
|
||||
█▄▄▄█▀ █▄▄▄▄█ █▄▄▄█▀ █ █▀ █ █ █ █ █▀ ▀█ █ ▀ █ █ █▀ █
|
||||
█ █ █ █ ▀▀▀ █ █▀▀▀▀ █ █ █ █ █ █ ▄▀▀▀█ █ █▀▀▀▀
|
||||
█ █ █ █ █ ▀█▄▄▀ █ █ █ ██▄█▀ ▀▄▄ ▀▄▄▀█ ▀▄▄ ▀█▄▄▀
|
||||
█
|
||||
EOF
|
||||
);
|
||||
return $this->app->handle($_SERVER['argv']);
|
||||
|
||||
$output = new ClimateOutput();
|
||||
|
||||
try {
|
||||
return $this->app->run(new ArgvInput(), $output);
|
||||
} catch (\Exception $e) {
|
||||
$output->error($e->getMessage());
|
||||
$output->error($e->getTraceAsString());
|
||||
|
||||
return $e->getCode() ?: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
210
src/Cli/ClimateOutput.php
Normal file
210
src/Cli/ClimateOutput.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli;
|
||||
|
||||
use League\CLImate\CLImate;
|
||||
use League\CLImate\TerminalObject\Dynamic\Spinner;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatter;
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* @method CLImate black(string $str = null)
|
||||
* @method CLImate red(string $str = null)
|
||||
* @method CLImate green(string $str = null)
|
||||
* @method CLImate yellow(string $str = null)
|
||||
* @method CLImate blue(string $str = null)
|
||||
* @method CLImate magenta(string $str = null)
|
||||
* @method CLImate cyan(string $str = null)
|
||||
* @method CLImate lightGray(string $str = null)
|
||||
* @method CLImate darkGray(string $str = null)
|
||||
* @method CLImate lightRed(string $str = null)
|
||||
* @method CLImate lightGreen(string $str = null)
|
||||
* @method CLImate lightYellow(string $str = null)
|
||||
* @method CLImate lightBlue(string $str = null)
|
||||
* @method CLImate lightMagenta(string $str = null)
|
||||
* @method CLImate lightCyan(string $str = null)
|
||||
* @method CLImate white(string $str = null)
|
||||
*
|
||||
* @method CLImate backgroundBlack(string $str = null)
|
||||
* @method CLImate backgroundRed(string $str = null)
|
||||
* @method CLImate backgroundGreen(string $str = null)
|
||||
* @method CLImate backgroundYellow(string $str = null)
|
||||
* @method CLImate backgroundBlue(string $str = null)
|
||||
* @method CLImate backgroundMagenta(string $str = null)
|
||||
* @method CLImate backgroundCyan(string $str = null)
|
||||
* @method CLImate backgroundLightGray(string $str = null)
|
||||
* @method CLImate backgroundDarkGray(string $str = null)
|
||||
* @method CLImate backgroundLightRed(string $str = null)
|
||||
* @method CLImate backgroundLightGreen(string $str = null)
|
||||
* @method CLImate backgroundLightYellow(string $str = null)
|
||||
* @method CLImate backgroundLightBlue(string $str = null)
|
||||
* @method CLImate backgroundLightMagenta(string $str = null)
|
||||
* @method CLImate backgroundLightCyan(string $str = null)
|
||||
* @method CLImate backgroundWhite(string $str = null)
|
||||
*
|
||||
* @method CLImate bold(string $str = null)
|
||||
* @method CLImate dim(string $str = null)
|
||||
* @method CLImate underline(string $str = null)
|
||||
* @method CLImate blink(string $str = null)
|
||||
* @method CLImate invert(string $str = null)
|
||||
* @method CLImate hidden(string $str = null)
|
||||
*
|
||||
* @method CLImate info(string $str = null)
|
||||
* @method CLImate comment(string $str = null)
|
||||
* @method CLImate whisper(string $str = null)
|
||||
* @method CLImate shout(string $str = null)
|
||||
* @method CLImate error(string $str = null)
|
||||
*
|
||||
* @method mixed out(string $str)
|
||||
* @method mixed inline(string $str)
|
||||
* @method mixed table(array $data)
|
||||
* @method mixed json(mixed $var)
|
||||
* @method mixed br($count = 1)
|
||||
* @method mixed tab($count = 1)
|
||||
* @method mixed draw(string $art)
|
||||
* @method mixed border(string $char = null, integer $length = null)
|
||||
* @method mixed dump(mixed $var)
|
||||
* @method mixed flank(string $output, string $char = null, integer $length = null)
|
||||
* @method mixed progress(integer $total = null)
|
||||
* @method Spinner spinner(string $label = null, string ...$characters = null)
|
||||
* @method mixed padding(integer $length = 0, string $char = '.')
|
||||
* @method mixed columns(array $data, $column_count = null)
|
||||
* @method mixed clear()
|
||||
* @method CLImate clearLine()
|
||||
*
|
||||
* @method CLImate addArt(string $dir)
|
||||
*/
|
||||
class ClimateOutput extends ConsoleSectionOutput implements ConsoleOutputInterface
|
||||
{
|
||||
private CLImate $CLImate;
|
||||
|
||||
private OutputFormatterInterface|OutputFormatter $formatter;
|
||||
|
||||
private int $verbosity = self::VERBOSITY_NORMAL;
|
||||
|
||||
private bool $decorated = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->CLImate = new CLImate();
|
||||
$this->formatter = new OutputFormatter($this->isDecorated());
|
||||
|
||||
// create stream output
|
||||
$output = fopen('php://stdout', 'w');
|
||||
stream_set_blocking($output, true);
|
||||
|
||||
$sections = [];
|
||||
|
||||
parent::__construct(
|
||||
$output,
|
||||
$sections,
|
||||
$this->getVerbosity(),
|
||||
$this->isDecorated(),
|
||||
$this->getFormatter()
|
||||
);
|
||||
}
|
||||
|
||||
public function write(iterable|string $messages, bool $newline = false, int $options = 0): void
|
||||
{
|
||||
if (is_string($messages)) {
|
||||
$messages = [$messages];
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$message = $this->formatter->format($message);
|
||||
if (!$newline) {
|
||||
$this->CLImate->inline($message);
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->CLImate->out($message);
|
||||
}
|
||||
}
|
||||
|
||||
protected function doWrite(string $message, bool $newline): void
|
||||
{
|
||||
if ($newline) {
|
||||
$this->CLImate->out($message);
|
||||
} else {
|
||||
$this->CLImate->inline($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function setVerbosity(int $level): void
|
||||
{
|
||||
$this->verbosity = $level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVerbosity(): int
|
||||
{
|
||||
return $this->verbosity;
|
||||
}
|
||||
|
||||
public function isQuiet(): bool
|
||||
{
|
||||
return $this->verbosity === self::VERBOSITY_QUIET;
|
||||
}
|
||||
|
||||
public function isVerbose(): bool
|
||||
{
|
||||
return $this->verbosity >= self::VERBOSITY_VERBOSE;
|
||||
}
|
||||
|
||||
public function isVeryVerbose(): bool
|
||||
{
|
||||
return $this->verbosity >= self::VERBOSITY_VERY_VERBOSE;
|
||||
}
|
||||
|
||||
public function isDebug(): bool
|
||||
{
|
||||
return $this->verbosity >= self::VERBOSITY_DEBUG;
|
||||
}
|
||||
|
||||
public function setDecorated(bool $decorated): void
|
||||
{
|
||||
$this->decorated = $decorated;
|
||||
}
|
||||
|
||||
public function isDecorated(): bool
|
||||
{
|
||||
return $this->decorated;
|
||||
}
|
||||
|
||||
public function setFormatter(OutputFormatterInterface $formatter): void
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
public function getFormatter(): OutputFormatterInterface
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
public function __call(string $name, array $arguments)
|
||||
{
|
||||
return $this->CLImate->$name(...$arguments);
|
||||
}
|
||||
|
||||
public function getErrorOutput(): OutputInterface
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setErrorOutput(OutputInterface $error): void
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public function section(): ConsoleSectionOutput
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
21
src/Cli/Commands/Command.php
Normal file
21
src/Cli/Commands/Command.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands;
|
||||
|
||||
use Symfony\Component\Console\Helper\HelperSet;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
|
||||
abstract class Command extends \Symfony\Component\Console\Command\Command implements CommandInterface
|
||||
{
|
||||
protected QuestionHelper $helper;
|
||||
|
||||
public function __construct(?string $name = null, ?callable $code = null)
|
||||
{
|
||||
parent::__construct($name, $code);
|
||||
|
||||
$this->helper = new QuestionHelper();
|
||||
$this->setHelperSet(new HelperSet([$this->helper]));
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,20 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands;
|
||||
|
||||
use Siteworxpro\App\Cli\ClimateOutput;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
interface CommandInterface
|
||||
{
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param ArgvInput|ClimateOutput $input
|
||||
* @param ClimateOutput $output
|
||||
* @return int
|
||||
*/
|
||||
public function execute(): int;
|
||||
public function __invoke(
|
||||
ClimateOutput|ArgvInput $input,
|
||||
ClimateOutput $output
|
||||
): int;
|
||||
}
|
||||
|
||||
@@ -4,40 +4,43 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands;
|
||||
|
||||
use Ahc\Cli\Input\Command;
|
||||
use League\CLImate\TerminalObject\Dynamic\Progress;
|
||||
use Siteworxpro\App\Cli\ClimateOutput;
|
||||
use Siteworxpro\App\CommandBus\Commands\ExampleCommand;
|
||||
use Siteworxpro\App\Services\Facades\CommandBus;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DemoCommand extends Command implements CommandInterface
|
||||
#[AsCommand('demo', 'A demo command to showcase the CLI functionality.')]
|
||||
class DemoCommand extends Command
|
||||
{
|
||||
public function __construct()
|
||||
protected function configure(): void
|
||||
{
|
||||
parent::__construct('api:demo', 'A demo command to showcase the CLI functionality.');
|
||||
|
||||
$this->argument('[name]', 'Your name')
|
||||
->option('-g, --greet', 'Include a greeting message');
|
||||
$this->addArgument('name', null, 'Your name');
|
||||
$this->addOption('greet', 'g', null, 'Include a greeting message');
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
public function __invoke(ClimateOutput|ArgvInput $input, ClimateOutput|OutputInterface $output): int
|
||||
{
|
||||
$pb = $this->progress(100);
|
||||
/** @var Progress $pb */
|
||||
$pb = $output->progress(100);
|
||||
|
||||
for ($i = 0; $i < 100; $i += 10) {
|
||||
usleep(100000); // Simulate work
|
||||
$pb->advance(10);
|
||||
}
|
||||
|
||||
$pb->finish();
|
||||
$output->bold()->blue("Demo Command Executed!\n");
|
||||
$name = $input->getArgument('name');
|
||||
|
||||
$this->writer()->boldBlue("Demo Command Executed!\n");
|
||||
$name = $this->values()['name'];
|
||||
$greet = $this->values()['greet'] ?? false;
|
||||
$greet = $input->getOption('greet') !== null;
|
||||
|
||||
if ($greet) {
|
||||
$this->writer()->green("Hello, $name! Welcome to the CLI demo.\n");
|
||||
$output->green("Hello, $name! Welcome to the CLI demo.\n");
|
||||
} else {
|
||||
$exampleCommand = new ExampleCommand($name);
|
||||
$this->writer()->yellow(CommandBus::handle($exampleCommand));
|
||||
$output->yellow(CommandBus::handle($exampleCommand));
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -4,24 +4,29 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\Queue;
|
||||
|
||||
use Ahc\Cli\Input\Command;
|
||||
use Siteworxpro\App\Async\Consumer;
|
||||
use Siteworxpro\App\Async\Messages\SayHelloMessage;
|
||||
use Siteworxpro\App\Cli\Commands\CommandInterface;
|
||||
use Siteworxpro\App\Cli\ClimateOutput;
|
||||
use Siteworxpro\App\Cli\Commands\Command;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
|
||||
class Start extends Command implements CommandInterface
|
||||
#[AsCommand('queue:start', 'Start the queue consumer to process messages.')]
|
||||
class Start extends Command
|
||||
{
|
||||
public function __construct()
|
||||
protected function configure(): void
|
||||
{
|
||||
parent::__construct('queue:start', 'Start the queue consumer to process messages.');
|
||||
$this->argument('[queues]', 'The name of the queue to consume from. ex. "first_queue,second_queue"');
|
||||
$this->addArgument(
|
||||
'queues',
|
||||
null,
|
||||
'The name of the queue to consume from. ex. "first_queue,second_queue"'
|
||||
);
|
||||
}
|
||||
|
||||
public function execute(): int
|
||||
public function __invoke(ClimateOutput|ArgvInput $input, ClimateOutput $output): int
|
||||
{
|
||||
$queues = [];
|
||||
if ($this->values()['queues'] !== null) {
|
||||
$queues = explode(',', $this->values()['queues']);
|
||||
if ($input->getArgument('queues') !== null) {
|
||||
$queues = explode(',', $input->getArgument('queues'));
|
||||
}
|
||||
|
||||
$consumer = new Consumer($queues);
|
||||
|
||||
@@ -4,28 +4,22 @@ declare(strict_types=1);
|
||||
|
||||
namespace Siteworxpro\App\Cli\Commands\Queue;
|
||||
|
||||
use Ahc\Cli\Input\Command;
|
||||
use Siteworxpro\App\Async\Messages\SayHelloMessage;
|
||||
use Siteworxpro\App\Cli\Commands\CommandInterface;
|
||||
use Siteworxpro\App\Cli\ClimateOutput;
|
||||
use Siteworxpro\App\Cli\Commands\Command;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class TestJob
|
||||
*
|
||||
* A CLI command to schedule a demo job that dispatches a SayHelloMessage.
|
||||
*/
|
||||
class TestJob extends Command implements CommandInterface
|
||||
#[AsCommand('queue:test-job', 'Dispatches a SayHelloMessage to demonstrate queue functionality')]
|
||||
class TestJob extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('queue:demo', 'Schedule a demo job.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command to dispatch a SayHelloMessage.
|
||||
*
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function execute(): int
|
||||
public function __invoke(ClimateOutput|ArgvInput $input, ClimateOutput|OutputInterface $output): int
|
||||
{
|
||||
SayHelloMessage::dispatch('World from TestJob Command!');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user