diff --git a/.ai-usage-info.md b/.ai-usage-info.md index 253ace2..9202bf9 100644 --- a/.ai-usage-info.md +++ b/.ai-usage-info.md @@ -34,8 +34,9 @@ Haupteinstieg zum Registrieren und Starten von Command-Gruppen. ### CliApplication und Scopes `new CliApplication()` erstellt einen eigenen Command-Satz. Mit `$app->addClass(Foo::class)` werden Action-Klassen registriert; mit -`$app->run(['tool', 'scope', 'action'])` wird die Ausgabe als String -zurückgegeben. Exceptions gelangen zum Aufrufer. Verschiedene Instanzen +`$app->run(['tool', 'scope', 'action'])` wird direkt auf die Konsole ausgegeben, +ohne PHP-Output-Buffer zu aktivieren oder zu verändern. Exceptions gelangen zum +Aufrufer. Verschiedene Instanzen teilen keine Command-Registrierung. `#[CliScope('scope')]` auf mehreren Klassen fügt ihre Actions unter einem Gruppennamen zusammen; jede Action benutzt ihre eigene Klasse und deren Konstruktor. Ohne Attribut gilt weiter @@ -127,7 +128,7 @@ Interne Command-Strukturen für Dispatching und Parameter-Mapping. ## Empfehlung Für neue CLI-Programme in diesem Paket normalerweise diese Bausteine verwenden: -- `CliApplication` für voneinander unabhängige CLI-Programme und auswertbare Ausgabe +- `CliApplication` für voneinander unabhängige CLI-Programme mit direkter Ausgabe - `CliDispatcher` für bestehende statische Actions - `CliParameter` für Parameterbeschreibung - `In` für Eingaben diff --git a/README.md b/README.md index 0aa2e60..3edf6fb 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,10 @@ Weitere Übersicht: `.ai-usage-info.md` `CliApplication` verwaltet eine eigene Command-Registry. `run()` nimmt wie `CliDispatcher::run()` ein `argv` einschließlich Programmname entgegen und -liefert die Konsolenausgabe als String. Fehler werden als Exceptions an den -Aufrufer weitergegeben. Die bisherigen statischen `CliDispatcher`-Aufrufe -funktionieren weiterhin und schreiben die Ausgabe direkt auf die Konsole. +schreibt die Ausgabe direkt auf die Konsole. Dabei wird kein PHP-Output-Buffer +aktiviert oder verändert. Fehler werden als Exceptions an den Aufrufer +weitergegeben. Die bisherigen statischen `CliDispatcher`-Aufrufe funktionieren +weiterhin ebenfalls mit direkter Konsolenausgabe. ```php use Phore\Cli\Annotation\CliParameter; @@ -88,7 +89,7 @@ class ProjectStatus $app = new CliApplication(); $app->addClass(ProjectCreate::class); $app->addClass(ProjectStatus::class); -echo $app->run(['tool', 'project', 'create', '--template-dir=/tmp/basic']); +$app->run(['tool', 'project', 'create', '--template-dir=/tmp/basic']); ``` Ergebnis: `Template: /tmp/basic`. Ohne `CliScope` bleibt der kleingeschriebene diff --git a/examples/shared-scopes.php b/examples/shared-scopes.php index 7b1c417..be367bf 100644 --- a/examples/shared-scopes.php +++ b/examples/shared-scopes.php @@ -35,5 +35,5 @@ public function status(): void $app->addClass(ProjectCreateActions::class); $app->addClass(ProjectStatusActions::class); -// run() liefert die Ausgabe zurück; Exceptions gehen an den Aufrufer. -echo $app->run($argv); +// run() schreibt direkt auf die Konsole; Exceptions gehen an den Aufrufer. +$app->run($argv); diff --git a/spec/CliApplicationSpec.php b/spec/CliApplicationSpec.php index b237226..face169 100644 --- a/spec/CliApplicationSpec.php +++ b/spec/CliApplicationSpec.php @@ -11,6 +11,8 @@ #[CliScope('project')] class CreateAction { + public static ?int $bufferLevel = null; + public function __construct(#[CliParameter] private string $workspace = './build') { } @@ -24,6 +26,11 @@ public function inspect(array $argv): void { echo implode(',', $argv); } + + public function buffer(): void + { + self::$bufferLevel = ob_get_level(); + } } #[CliScope('project')] @@ -50,9 +57,16 @@ public function it_combines_actions_from_classes_in_the_same_scope(): void $this->addClass(CreateAction::class); $this->addClass(StatusAction::class); - $this->run(['tool', 'project', '--workspace', '/tmp', 'create', '--template-dir=basic']) - ->shouldReturn("/tmp:basic\n"); - $this->run(['tool', 'project', 'status'])->shouldReturn("ready\n"); + $bufferLevel = ob_get_level(); + CreateAction::$bufferLevel = null; + + $this->run(['tool', 'project', '--workspace', '/tmp', 'create', '--template-dir=basic']); + $this->run(['tool', 'project', 'buffer']); + $this->run(['tool', 'project', 'status']); + + if (CreateAction::$bufferLevel !== $bufferLevel || ob_get_level() !== $bufferLevel) { + throw new \RuntimeException('CliApplication::run() must not change PHP output buffering.'); + } } public function it_keeps_registrations_in_separate_instances(): void @@ -61,8 +75,8 @@ public function it_keeps_registrations_in_separate_instances(): void $other->addClass(StatusAction::class); $this->addClass(CreateAction::class); - $this->run(['tool', 'project', 'inspect', '0'])->shouldReturn("0\n"); - $other->run(['tool', 'project', 'status'])->shouldReturn("ready\n"); + $this->run(['tool', 'project', 'inspect', '0']); + $other->run(['tool', 'project', 'status']); $this->shouldThrow(CliException::class) ->during('run', [['tool', 'project', 'status']]); } diff --git a/src/CliApplication.php b/src/CliApplication.php index e84e3f9..fe6e609 100644 --- a/src/CliApplication.php +++ b/src/CliApplication.php @@ -49,19 +49,20 @@ public function addClass(string $className): void } /** - * Executes argv and returns buffered echo output from the command or help. + * Executes argv and writes command or help output directly to the current output stream. * * The first element is the executable name. Exceptions propagate to the - * caller; no output is returned on failure. Action side effects still occur. + * caller. The method deliberately does not start or manipulate PHP output + * buffering, so embedding applications retain full control over output. * - * Example: echo $app->run(['tool', 'project', 'create', '--name', 'demo']); + * Example: $app->run(['tool', 'project', 'create', '--name', 'demo']); * * @param array $argv - * @return string Captured echo output followed by a newline. + * @return void * @throws \Throwable * @see CliDispatcher::run() */ - public function run(array $argv): string + public function run(array $argv): void { $name = array_shift($argv); $this->commandSet->setName($name ?? ''); @@ -82,14 +83,7 @@ public function run(array $argv): string } } - ob_start(); - try { - $this->commandSet->dispatch($argv, $arguments); - echo "\n"; - return (string) ob_get_clean(); - } catch (\Throwable $exception) { - ob_end_clean(); - throw $exception; - } + $this->commandSet->dispatch($argv, $arguments); + echo "\n"; } } diff --git a/src/CliDispatcher.php b/src/CliDispatcher.php index e0f36d6..9758d64 100644 --- a/src/CliDispatcher.php +++ b/src/CliDispatcher.php @@ -44,7 +44,7 @@ public static function addClass(string $className): void public static function run(array $argv, int $argc = null): void { try { - echo self::getDefault()->run($argv); + self::getDefault()->run($argv); } catch (\Exception|\Error $exception) { (new ExceptionVisualizer())->visualize($exception); }