15 years ago...
June 14, 2008
symfony 1.1 introduces "tasks"
Slide 4
Slide 4 text
Console
is one of the oldest
Symfony Component
Slide 5
Slide 5 text
Console
is the most used/downloaded
Symfony Component
Slide 6
Slide 6 text
Symfony Console
> Commands
Input: parses CLI arguments/options
Code: your own business logic
Output: prints, redirect, pipe
> Low-level terminal abstractions
Cursor
Color
Style
...
Slide 7
Slide 7 text
namespace Symfony\Component\Console;
final class Color
{
public function __construct(string $foreground = '', string $background = '', array $options = [])
bold
underscore
blink
reverse
conceal
Is it a color?
$color = new Color('#000', 'white', ['underscore', 'reverse']);
Color Class
Console 7.0
Slide 8
Slide 8 text
Introducing the
Terminal Component
Slide 9
Slide 9 text
Colors
Slide 10
Slide 10 text
class Color
{
public function __construct(
private readonly string $value,
) {
}
ANSI-4 16 colors
ANSI-256 256 colors
ANSI-24 "true colors"
Just a color!
$color = new Color('#000');
$color = new Color('red');
Color Class
Terminal 8.0
$color = $color->tint(20);
$color = $color->shade(20);
$color = $color->scale(20);
$color = $color->mix($anotherColor, 20);
Like Saas
$context = new Context(
colorMode: ColorMode::Ansi8,
);
Color
"degradation"
Slide 11
Slide 11 text
$color = new AdaptiveColor('#0f0', '#ff0');
$context = new Context(
darkMode: true,
);
Color adapts depending
on your terminal background
Light/Dark background?
Terminal 8.0
Light Dark
From Colors to Styles
namespace Symfony\Component\Console;
final class Color
{
public function __construct(string $foreground = '', string $background = '', array $options = [])
Slide 14
Slide 14 text
Styling, Formatting, ...
/**
* Formats a message within a section.
*
* @param string $section The section name
* @param string $text The text message
* @param integer $size The maximum size allowed for a line (65 by default)
*/
public function formatSection($section, $text, $size = null)
{
return sprintf(">> %-$9s %s", $section, $this->excerpt($text, $size));
}
/**
* Formats a message within a section.
*/
public function formatSection(string $section, string $message, string $style = 'info'): string
{
return sprintf('<%s>[%s]%s> %s', $style, $section, $style, $message);
}
Console 7.0 circa 2023
Tasks 1.1 circa 2008
$screen = new Columns(
'LEFT',
'RIGHT',
);
echo $screen->render();
$stylesheet['.screen'] = (new Style())
->width('terminal["width"]')
->height(20)
;
$screen = (new Columns(
new StyledContent('LEFT', $stylesheet['.cell w-2fr']),
new StyledContent('RIGHT', $stylesheet['.cell w-1fr']),
))->style($stylesheet['.screen']);
echo $screen->render();
$screen = (new Rows(
$screen,
$screen,
))->style($stylesheet['.screen']);
echo $screen->render();
Columns and Rows
Terminal 8.0
2fr 1fr
$stylesheet['.cell'] = (new Style())
->borderStyle(BorderStyle::ROUNDED)
->align(Position::CENTER)
->borderForeground('color("border-primary")')
;
Slide 37
Slide 37 text
$screen = (new Columns(
(new Rows(
new StyledContent('UP', $stylesheet['.cell']),
new StyledContent('MIDDLE', $stylesheet['.cell']),
new StyledContent(' LOW ', $stylesheet['.cell']),
))->position(Position::MIDDLE)->style($stylesheet['.cell w-1fr']),
new StyledContent('CENTER', $stylesheet['.cell w-1fr']),
new StyledContent(trim(str_repeat("RIGHT\n", 15)), $stylesheet['.cell w-1fr']),
))->position(Position::BOTTOM)->style($stylesheet['.screen']);
echo $screen->render();
Columns and Rows
Terminal 8.0
Slide 38
Slide 38 text
From Static to Interactive
with Widgets
Slide 39
Slide 39 text
interface WidgetInterface extends RenderInterface
{
public function init(Stylesheet $stylesheet, LoggerInterface $logger): void;
public function update(MessageInterface $message): ?MessageInterface;
}
interface RenderInterface
{
public function render(): string;
}
WidgetInterface
Terminal 8.0
ExitMessage
KeyMessage
MouseMessage
TickMessage
WindowSizeMessage
Slide 40
Slide 40 text
class HelloWorldWidget implements WidgetInterface
{
public function init(Stylesheet $stylesheet, LoggerInterface $logger): void
{
}
public function update(MessageInterface $message): ?MessageInterface
{
if ($message instanceof KeyMessage && 'q' === (string) $message->key) {
return new ExitMessage();
}
return null;
}
public function render(): string
{
return 'Hello World!';
}
}
$term = new Terminal();
$stylesheet = new DefaultStylesheet();
$app = new Application($term, $stylesheet, new HelloWorldWidget());
$app->run();
HelloWorldWidget Terminal 8.0
Render
Update the state
Infinite loop
Slide 41
Slide 41 text
A Pager Terminal 8.0
Supported keys
up and down
page up and down
go to top
go to bottom
Slide 42
Slide 42 text
public function __construct(string $content)
{
$this->viewport = new ViewportWidget();
$this->viewport->setContent($content, substr_count($content, "\n"));
}
public function init(Stylesheet $stylesheet, LoggerInterface $logger): void
{
$this->stylesheet = $stylesheet;
$this->viewport->init($stylesheet, $logger);
}
public function update(MessageInterface $message): ?MessageInterface
{
$this->viewport->update($message);
return null;
}
public function render(): string
{
return $this->stylesheet['span p-1 border-double']
->render($this->viewport->render());
}
A Pager Terminal 8.0
Update the Viewport
Propagate init()
Render the Viewport
Rely on another widget
Slide 43
Slide 43 text
$screen->enableMouse();
try {
$app->run();
} finally {
$screen->disableMouse();
}
Mouse support
Terminal 8.0
if ($message instanceof WindowSizeMessage) {
$this->viewport->width($message->width);
$this->viewport->height($message->height);
}
Window resizing support
Slide 44
Slide 44 text
Demo Time?
Terminal
Slide 45
Slide 45 text
A component with many sub-parts
Multi-year effort - release gradually 7.1 -> 8.0
Progressively use it internally in Console
Help from the community needed
Terminal
Slide 46
Slide 46 text
First Pull request: Empty "shell"
Terminal 8.0
Second one: Color sub-namespace