DEV Community

david duymelinck
david duymelinck

Posted on

Exploring the Symfony Tui component

I had seen the Symfony Tui component announcement, but I just got the time to start to explore it.

Setup

To explore the component fast I checked out the PR.
Added "App\\" : "src/App" to composer.json.
Created a command, TuiTestCommand, in the App directory, and a bin/console file with a console application and the command.

In the following sections I'm going to take baby steps to understand how the component works.

Returning a text

The standard way to return a text is $output->writeln('Hello world!');

To do this with the Tui component this is:

use Symfony\Component\Tui\Tui;
use Symfony\Component\Tui\Widget\TextWidget;

// in the command
$tui = new Tui();

$tui->add(new TextWidget('Hello World!'));

$tui->onTick(fn() => $tui->stop());

$tui->run();
Enter fullscreen mode Exit fullscreen mode

Because the component is meant to create rich interactive experiences it uses a bit more code to make it run.

You can look at the Tui class as an agent. You can give it rules and when called it runs those rules.
Do I have AI brain? Before AI I would probably bring up the Node event loop.

Once you call the run method, the loop starts.
So if the stop method is never called the only possibility to get out of the loop is closing the CLI.
In an actual application getting out of the loop will be less janky than in this example.

You could create a bin/MyAwesomeCodingAgent by setting the Tui code containing command as the default.

Styled text

The default way to add styling to a text is by adding faux tags.

$output->writeln('<fg=red;options=bold>Hello World!</>');
Enter fullscreen mode Exit fullscreen mode

In the Tui component the way of working with CSS is mimicked. Underneath it still uses the suffix and prefix codes, but that is not our problem anymore.

use Symfony\Component\Tui\Style\Style;

// in command

$redBoldStyle = new Style()->withColor('red')->withBold();

$text = new TextWidget('Hello World!');
$text->setStyle($redBoldStyle);
Enter fullscreen mode Exit fullscreen mode

This method is good for one-off cases. But when you want a bit more maintainability and consistency it is better to create a StyleSheet.

use Symfony\Component\Tui\Style\StyleSheet;

// in command

$stylesheet = new StyleSheet([
  '.red-bold' => new Style()->withColor('red')->withBold(true),
]);

$tui->addStyleSheet($stylesheet);

$text = new TextWidget('Hello World!');
$text->addStyleClass('red-bold');
Enter fullscreen mode Exit fullscreen mode

For the purpose of the test I added the StyleSheet object to the command, but in an actual application I would create a SomeCommandStyleSheet or a BrandStyleSheet that contains all the possible styles. Or both, there is no reason they shouldn't be composable.

Input

The default of adding an input to a command is:

$helper = new QuestionHelper();
$question = new Question('Please enter your email: ','');
$question->setValidator(function ($answer) {
   if (trim($answer) == '') {
      throw new \RuntimeException('Email address cannot be empty');
   }
   return $answer;
});

$email = $helper->ask($input, $output, $question);

$output->writeln("Email submitted: {$email}");
Enter fullscreen mode Exit fullscreen mode

Every time the user hits return without a value the question is being re-displayed together with the error text.

$tui = new Tui();

$emailInput = new InputWidget();
$emailInput->setPrompt('Please enter your email: ');
$emailInput->on(SubmitEvent::class, function (SubmitEvent $event) use ($tui, $output) {
    $email = $event->getValue();
    $statusText = $tui->getById('status-text');

    if ($event->isEmpty()) {
        $statusText->setText('Email address cannot be empty.');
    } else {
        $tui->stop();

        $output->writeln("Email submitted: {$email}");
    }
});

$tui->add($emailInput);

$statusText = new TextWidget('');
$statusText->setId('status-text');
$statusText->setStyle(new Style()->withColor('red')->withBold());

$tui->add($statusText);

$tui->run();
Enter fullscreen mode Exit fullscreen mode

The thing that became clear to me when creating the input code is that the InputInterface and OutputInterface are not tied to the Tui widgets like the helper classes we got used to.

Because of the widgets is feels more like building an HTML page than working with a text interface. So I'm very exited about the upcoming PR that makes it possible to use Twig templates.

The on method brings me back to the jQuery days.
And because of the event binding the input will only take the two lines initial lines on the screen, which is better that with the helper validation.

I barely scratched the surface, but I didn't want to make the post too long. Let me know if you want to see more of the Tui component.

Top comments (0)