Understanding the Symfony Console Component
Petre Pătraşc
@Cegeka
All code samples and slides available after presentation
PHP 7 ❤ Symfony 3
Components
- Dependency Injection
- Event Dispatcher
- Console
- etc...
Symfony Console
- Structure of a Command
- Input
- Output
- Visualizing the Role
- Case Study
Talk is cheap, show me the code
Hello World Command
petre$ bin/console demo:hello
Hello World!
petre$ bin/console demo:hello "Symfony Bucharest"
Hello Symfony Bucharest!
namespace SymfonyBucharest\UnderstandingConsoleBundle\Command;
use ...
/**
* Greet a person or group of people.
*/
class HelloWorldCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('demo:hello')
->setDescription('Greet a person or group of people')
->addArgument(
'who',
InputArgument::OPTIONAL,
'The person or group to greet',
'World'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$who = $input->getArgument('who');
$output->writeln("Hello {$who}!");
}
}
Writing a Command
- Create a PHP class under the Command namespace
- Extend ContainerAwareCommand
- Overwrite configure()
- Overwrite execute($input, $output)
Handling Input
- Input Arguments
- doctrine:mig:mig prev
- Input Options
- cache:clear -e=prod
Input Arguments
- Feed input into Command
- Can have multiple Arguments
- Some Arguments can be of type array
Input Arguments
$this
->setName('demo:hello')
->setDescription('Greet a person or group of people')
->addArgument(
'who', # Argument Identifier
InputArgument::OPTIONAL, # Required/Optional/Array
'The person or group to greet', # Description
'World' # Default Value
);
Input Arguments
petre$ bin/console demo:hello --help
Usage:
demo:hello [<who>]
Arguments:
who The person or group to greet [default: "World"]
Options:
-h, --help Display this help message
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi Force ANSI output
--no-ansi Disable ANSI output
-n, --no-interaction Do not ask any interactive question
-e, --env=ENV The Environment name. [default: "dev"]
--no-debug Switches off debug mode.
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Help:
Greet a person or group of people
Input Options
- Define multiple flags/modifiers
- Can use shortcut notation
$this->addOption(
'greeting', # Option Identifier
'g', # Option Shortcut
InputOption::VALUE_OPTIONAL, # Required/Optional/Array
'The greeting to use', # Description
'Hello' # Default Value
);
$who = $input->getArgument('who');
$greeting = $input->getOption('greeting');
$output->writeln("{$greeting} {$who}!");
configure()
execute()
Input Options
petre$ bin/console demo:hello "Symfony București" --greeting "Salut"
Salut Symfony București!
petre$ bin/console demo:hello "Symfony București" -g "Salut"
Salut Symfony București!
Output Options
- Coloring output
- Creating custom styles
- Helpers
Coloring Output
Coloring Output
Some simple styles are built-in
$output->writeln('<info>Information message</info>');
$output->writeln('<comment>A comment</comment>');
$output->writeln('<question>User interaction/Question</question>');
$output->writeln('<error>Errors, exceptions, all that good stuff</error>');
Roll your own
$yoloStyle = new OutputFormatterStyle('yellow', 'green', ['bold']);
$output->getFormatter()->setStyle('yolo', $yoloStyle);
$output->writeln('<yolo>Custom style</yolo>');
Helpers
- Question
- Progress Bar
- Table
- Fully extensible, add your own
Question Helper
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('<question>Continue?</question> ', false);
if (true === $helper->ask($input, $output, $question)) {
return 0;
} else {
return 1;
}
petre$ bin/console demo:helper:question
The answer to the Ultimate Question of Life, The Universe, and Everything is 42. Correct? no
You need to crunch the numbers again. See you in ten million years.
Progress Helper
petre$ bin/console demo:helper:progress -vvv
7/10 [===================>--------] 70% 6 secs/9 secs 8.0 MiB
$progress = new ProgressBar($output);
$progress->start(10);
for ($iterator = 0; $iterator < 10; $iterator++) {
$progress->advance();
sleep(1);
}
Table Helper
petre$ bin/console demo:helper:table
+--------------------------------------+------------+-----------+---------------------------------+
| ID | First Name | Last Name | Company |
+--------------------------------------+------------+-----------+---------------------------------+
| b142579b-9cd1-350e-a8ec-cc43c29718a6 | Lottie | Yost | Murray, Hamill and Koch |
| 06af6dd6-a5f3-3bd4-bfed-1628e26efdfe | Guido | Boyle | Gislason-McGlynn |
| 3b84e7a2-19c3-3e60-948b-203d94ac6b21 | Esta | Von | Kiehn-McClure |
| e133b0f3-b603-30dd-9c01-bd5a763cf87d | Fern | Denesik | Conn, Langosh and Buckridge |
| 291fdadf-7f13-3667-9c7b-f671e7beb447 | Ilene | McKenzie | Botsford, Kautzer and Schneider |
| 76e85603-ad97-3219-baa2-4b934dd79adc | Aisha | Crooks | Hudson Group |
| c34fbdeb-bc69-3fbc-84a0-16994720e3e1 | Brandt | Graham | Metz Group |
| 5d76dd0f-112a-394e-80cc-0c97e79f0fff | Dion | Bergstrom | Luettgen Group |
| 238cd2dd-34b1-39c4-9eee-a476d304a26c | Makayla | Mohr | Hyatt Ltd |
| ceb8362f-e579-388c-a272-dd756408693c | Conor | Schneider | Schmidt Group |
+--------------------------------------+------------+-----------+---------------------------------+
Table Helper
$faker = Factory::create();
$table = (new Table($output))
->setHeaders([
'ID',
'First Name',
'Last Name',
'Company',
]);
for ($iterator = 0; $iterator < 10; $iterator++) {
$table->addRow([
$faker->uuid,
$faker->firstName,
$faker->lastName,
$faker->company,
]);
}
$table->render();
Understand the history behind it
Growing Legacy Codebase
Many CLI scripts, all doing their own thing
Our App
I <3 reporting
I run for 3 days and import 10GB of data
Most CLI applications so far
- Decoupled from project codebase
- Perform basic maintenance operations
- Generate long-running reports
- No testing/no monitoring
CLI Apps with Console Component
- Integrated into existing codebase
- Can execute complex logic
- Can use other Components (DI, Event Dispatcher)
- Individual Commands are unit-testable
Integration into Symfony
Creating a Product
Product
Controller
Web User
Product
Service
Product
Repository
Data Store
(DB, API, etc.)
API Consumer
Product API Controller
CLI User
Product
Console Command
- Validate Input
- Call Business Logic
- Return Output
Integration
- Can access Container
- Services
- Parameters
- Using DI component gives access to other layers
- Tested Console code = tested production code
- Forces devs to isolate business logic strictly to business layer
Our App
Possible use cases
- Integrating with other OS workflows
- User joins team
- Create email
- Create website account
- User joins team
- Creating admin users securely
- Expiring Redis/Memcache keys
- Retrieving data from slow running API
- Monitoring
- Workers for processing queues
- Importing large data sets
Case Study
Context
- Working on feature with a lot of business value
- Need to build autocomplete for list of cities
- Data fetched from internal third-party API
- API only has listing on data, no searching options
- API is really slow
- Really slow
- 4 seconds for Belgium
- 144(!) seconds for France
- No resources available for refactoring.
What we did
- Added a provisioning layer in our architecture.
- When user gets details of a city, cache it in our infrastructure for 30 days.
- Cache data can be searched.
- Changed autocomplete logic.
- If city is found in cache, fetch details from there.
- If not, fetch from backend :(
- Create Command to fetch list of cities and "fetch" each of them.
- By fetching them all, we cache them all.
- Plug Command into pipeline
- Continuous Deployment
- Cronjob
Results
- Reduction from 4/144 seconds to 200ms
- Reuse 90% of our existing code
- Development took only a few hours
- Happy users
- Happy management
Wrap-Up
Reading - Symfony Console
- Console Component Symfony Book
- Dedicated Cookbook Articles
- Symfony Deep Dive: Console Component
Reading - Linux daemons, process signaling
Code
- https://github.com/petrepatrasc/understanding-symfony-console
Thank You!
Q&A
Copy of Understanding the Symfony Console
By Muhammad Taqi Hassan Bukhari
Copy of Understanding the Symfony Console
A presentation for the Symfony Bucharest meet-up.
- 735