SYMFONY CONSOLE: BUILD
AWESOME COMMAND LINE
SCRIPTS WITH EASE
Oscar Merida
August 1, 2017
GROUND RULES
IN THIS TALK
You’ll learn:
How to create your own CLI scrips like Composer and Drupal Console.
How to add commands
How to get input from users, display feedback
How to package it as a standalone le.
In this talk, I’ll show you how you can create a command line application like composer or drupal console to make your life easier.
WHY? TO AUTOMATE TASKS.
Prevent errors on routine tasks and speed them up too!
If you do something frequently or if a task has a lot of steps, computers are the perfect tool to perform them repetitively.
IS IT WORTH THE TIME?
From https://xkcd.com/1205/
Before you automate all the things, check if its worth sinking a lot of time into it. It makes sense to spend time automating the things you do very
frequently and take long to do. If a task is particularly error prone, automate it too. As usual, there’s an XKCD comic to reference.
EXAMPLES
Generate a new set of tickets in an issue tracker.
Prepare a mailchimp template for a campaign.
Generate images to share on social media.
Combine CSV les into Excel Spreadsheet.
What kind of tasks can you automate? With the right packages, almost anything. These are some examples of tasks I’ve automated to save myself
time.
MAKING A CLI APP
DIRECTORY SETUP
├── app
│ ├── resources
│ ├── src
│ └── vendor
└── build
This is a structure I typically use. All code goes in app, later we’ll use the build directory to creat a phar archive. resources holds templates,
images, other files commands might need. src is where your application’s commands will live.
INSTALLING SYMFONY CONSOLE
cd app
composer require symfony/console
Symfony’s console component is easily installed via composer. Issue this command, and it along with its dependencies will be downloaded to your
app/vendor folder.
AUTOLOADER
Create a namespace and directory for your application under src
"autoload": {
"psr-4": {
"GovCon": "src/GovCon",
}
}
Leverage composer’s PSR-4 autoloader to find your commands and code. We map our GovCon namespace to files in the src/GovCon directory.
Remember to run composer dump-autoload after adding this section to composer.json.
YOUR APP
Create app/govcon.php.
<?php
require_once __DIR__ . '/vendor/autoload.php';
use SymfonyComponentConsoleApplication;
// now run it
$application = new Application();
$application->run();
This is the file to execute to run a command. We include our autolaoder, then create an Application object and run() it. Of course, it doesn’t do
much at this point.
ADDING A COMMAND
Let’s add on to display the current time.
<?php
namespace GovCon;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;
class WhatTime extends Command {
}
We extend the base Command class. We must implement the configure() and execute() methods.
CONFIGURING COMMANDS
protected function configure() {
$this->setName('gc:whatTime')
->setDescription('Display the current time.')
->setHelp('Print the current time to STDOUT') //opt
;
}
This command sets the name to use to execute a command. The description is shown when a list of all commands is shown.
EXECUTE() METHOD
public function execute(InputInterface $input, OutputInterface $output)
{
$now = new DateTime();
$output->writeln('It is now ' . $now->format('g:i a'));
}
This is where we do any of the work needed.
NOW RUN IT:
app$ php govcon.php gc:whatTime
It is now 2:56 pm
GET USER INPUT
Questions let us get interactive input from the user.
use SymfonyComponentConsoleQuestionQuestion;
public function execute(InputInterface $input, OutputInterface $output)
{
$helper = $this->getHelper('question');
$default = 'Human';
$outputQ = new Question('What is your name? [' . $default. ']: ', $default);
$name = $helper->ask($input, $output, $outputQ);
$now = new DateTime();
$output->writeln('It is now ' . $now->format('g:i a') . ', ' . $name);
}
RESULT
app$ php govcon.php gc:whatTime
Waht is your name? [Human]: Oscar
It is now 3:02 pm, Oscar
A NOTE ON OUTPUT
We saw how to use $output->writeln() to send output. There’s also
$output->write()
You can also colorize your output:
$output->writeln('<info>Starting processing</info>'; // green
$output->writeln('<comment>Starting processing</comment>'; // yellow
$output->writeln('<question>Starting processing</question>'; // black on cyan
$output->writeln('<error>Starting processing</>'; // white on red
POSITIONAL ARGUMENTS
We can con gure command line arguments to get input too. They can be
requred or options
php govcon.php gc:whatTime "F, j Y h:i:s"
What is your name? [Human]:
It is now July, 31 2017 03:13:26, Human
ARGUMENTS, CONFIGURE()
use SymfonyComponentConsoleInputInputArgument;
protected function configure() {
$this->setName('gc:whatTime')
->setDescription('Display the current time.')
->addArgument('date_format', InputArgument::REQUIRED, 'Date format')
;
}
ARGUMENTS, EXECUTE()
$format = $input->getArgument('date_format');
$now = new DateTime();
$output->writeln('It is now ' . $now->format($format) . ', ' . $name);
SWITCHES AKA COMMAND OPTIONS
Another way to get user input.
app$ php govcon.php gc:whatTime --format "H:i:s"
What is your name? [Human]:
It is now 15:19:31, Human
OPTIONS, CONFIGURE
use SymfonyComponentConsoleInputInputOption; // earlier
// ...
$this->setName('gc:whatTime')
->setDescription('Display the current time.')
->setHelp('Print the current time to STDOUT')
->addOption('format',
'f', // shorcut
InputOption::VALUE_OPTIONAL,
'Date format'
)
;
OPTIONS, EXECUTE
$format = $input->getOption('format') ?? 'g:i a';
$now = new DateTime();
$output->writeln(
'It is now ' . $now->format($format) . ', ' . $name
);
CHOICES AND QUESTIONS
Finally, you can let users choose from one or more options.
app$ php govcon.php gc:whatTime --format "H:i:s"
Which format do you prefer?
[military] H:i
[common ] g:i a
> military
It is now 15:27
CHOICE, EXECUTE
$choices = [
'military' => 'H:i',
'common' => 'g:i a',
];
$question = new ChoiceQuestion(
'<question>Which format do you prefer?</question> ', $choices
);
$helper = $this->getHelper('question');
$format = $helper->ask($input, $output, $question);
$now = new DateTime();
$output->writeln('It is now ' . $now->format($choices[$format]));
CONFIGURATION FILES
Use a con guration le to store credentials, or persist values between
runs.
->addOption('config',
null
InputOption::VALUE_REQUIRED,
'Configuration Files'
);
$file = $input->getOption('config') ?? '~/govcon.config.php';
$values = $this->readConfig($file); // parse it
The format for your config file can be whatever you can parse that is easily writeable by your users. It can be a simple ini file, YAML, json, xml, etc.
CREATING A .PHAR
A WHAT?
A Phar archive is used to distribute a complete PHP
application or library in a single le.
For the command line, a phar file gives us away to distribute ALL our files inside a single file. The PHP interpreter knows how to work with it. You’re
probably already familiar with at least one phar - composer.phar
BUILD SKELETON
There are multiple ways to build one, I’ll show you how I do it. See also
packaging Your Apps with Phar
BUILD-PHAR.PHP
<?php
define ('BASE_SRC', realpath(__DIR__ . '/../app') . '/');
$name = 'govcon.phar';
$app = 'govcon.php';
if (file_exists($name)) {
unlink($name);
}
$phar = new Phar($name, 0, $name);
$phar->setSignatureAlgorithm(Phar::SHA1);
// add everything under our APP dir
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(BASE_SRC, FilesystemIterator::SKIP_DOTS)
);
$phar->buildFromIterator($it, BASE_SRC);
Explain what this does
HAVE YOURSELF A PHAR
Once the build comopletes, you’ll have a govcon.phar le.
build$ ./govcon.phar gc:whatTime
Which format do you prefer?
[military] H:i
[common ] g:i a
> military
It is now 15:40
You can share this file with others. Place it somewhere in there $PATH and it’ll be available globally.
WORKING WITH FILES
In your app le get the path to resources/ and inject it to any comand
which needs it.
$resource_dir = __DIR__ . '/resources';
$application->add(new FooSomeCommand($resource_dir));
Use DirectoryIterator not glob() to get list of les
// copy over resources for this column
// phar doesn't support glob
$files = new DirectoryIterator($this->resource_dir . "/myfiles/");
foreach ($files as $file) {
}
If you’re copying files from inside your archive to the user’s file system, use the DirectoryIterorator.
THIRD PARTY LIBRARIES
Not all libraries may work with bundled les in a phar.
In my case, I couldn’t bundle some font files for the Intervention library to use for image manipulation.
SECURITY
If you need to use api_keys or credentials, don’t put them in your phar le.
Use a con guration le and make sure its locked down so only the current
user can read it.
CONCLUSION
Automation can save you time and prevent errors. Symfony Console
handles a lot of the plumbing so you can focus on your use case.
THANK YOU
@omerida
I publish php[architect], a monthly magazine for PHP developers. Check it
out: www.phparch.com
php[world] is our fall conference in Tyson’s Corner. world.phparch.com

Symfony console: build awesome command line scripts with ease

  • 1.
    SYMFONY CONSOLE: BUILD AWESOMECOMMAND LINE SCRIPTS WITH EASE Oscar Merida August 1, 2017
  • 2.
  • 3.
    IN THIS TALK You’lllearn: How to create your own CLI scrips like Composer and Drupal Console. How to add commands How to get input from users, display feedback How to package it as a standalone le. In this talk, I’ll show you how you can create a command line application like composer or drupal console to make your life easier.
  • 4.
    WHY? TO AUTOMATETASKS. Prevent errors on routine tasks and speed them up too! If you do something frequently or if a task has a lot of steps, computers are the perfect tool to perform them repetitively.
  • 5.
    IS IT WORTHTHE TIME? From https://xkcd.com/1205/ Before you automate all the things, check if its worth sinking a lot of time into it. It makes sense to spend time automating the things you do very frequently and take long to do. If a task is particularly error prone, automate it too. As usual, there’s an XKCD comic to reference.
  • 6.
    EXAMPLES Generate a newset of tickets in an issue tracker. Prepare a mailchimp template for a campaign. Generate images to share on social media. Combine CSV les into Excel Spreadsheet. What kind of tasks can you automate? With the right packages, almost anything. These are some examples of tasks I’ve automated to save myself time.
  • 7.
  • 8.
    DIRECTORY SETUP ├── app │├── resources │ ├── src │ └── vendor └── build This is a structure I typically use. All code goes in app, later we’ll use the build directory to creat a phar archive. resources holds templates, images, other files commands might need. src is where your application’s commands will live.
  • 9.
    INSTALLING SYMFONY CONSOLE cdapp composer require symfony/console Symfony’s console component is easily installed via composer. Issue this command, and it along with its dependencies will be downloaded to your app/vendor folder.
  • 10.
    AUTOLOADER Create a namespaceand directory for your application under src "autoload": { "psr-4": { "GovCon": "src/GovCon", } } Leverage composer’s PSR-4 autoloader to find your commands and code. We map our GovCon namespace to files in the src/GovCon directory. Remember to run composer dump-autoload after adding this section to composer.json.
  • 11.
    YOUR APP Create app/govcon.php. <?php require_once__DIR__ . '/vendor/autoload.php'; use SymfonyComponentConsoleApplication; // now run it $application = new Application(); $application->run(); This is the file to execute to run a command. We include our autolaoder, then create an Application object and run() it. Of course, it doesn’t do much at this point.
  • 12.
    ADDING A COMMAND Let’sadd on to display the current time. <?php namespace GovCon; use SymfonyComponentConsoleCommandCommand; use SymfonyComponentConsoleInputInputInterface; use SymfonyComponentConsoleOutputOutputInterface; class WhatTime extends Command { } We extend the base Command class. We must implement the configure() and execute() methods.
  • 13.
    CONFIGURING COMMANDS protected functionconfigure() { $this->setName('gc:whatTime') ->setDescription('Display the current time.') ->setHelp('Print the current time to STDOUT') //opt ; } This command sets the name to use to execute a command. The description is shown when a list of all commands is shown.
  • 14.
    EXECUTE() METHOD public functionexecute(InputInterface $input, OutputInterface $output) { $now = new DateTime(); $output->writeln('It is now ' . $now->format('g:i a')); } This is where we do any of the work needed.
  • 15.
    NOW RUN IT: app$php govcon.php gc:whatTime It is now 2:56 pm
  • 16.
    GET USER INPUT Questionslet us get interactive input from the user. use SymfonyComponentConsoleQuestionQuestion; public function execute(InputInterface $input, OutputInterface $output) { $helper = $this->getHelper('question'); $default = 'Human'; $outputQ = new Question('What is your name? [' . $default. ']: ', $default); $name = $helper->ask($input, $output, $outputQ); $now = new DateTime(); $output->writeln('It is now ' . $now->format('g:i a') . ', ' . $name); }
  • 17.
    RESULT app$ php govcon.phpgc:whatTime Waht is your name? [Human]: Oscar It is now 3:02 pm, Oscar
  • 18.
    A NOTE ONOUTPUT We saw how to use $output->writeln() to send output. There’s also $output->write() You can also colorize your output: $output->writeln('<info>Starting processing</info>'; // green $output->writeln('<comment>Starting processing</comment>'; // yellow $output->writeln('<question>Starting processing</question>'; // black on cyan $output->writeln('<error>Starting processing</>'; // white on red
  • 19.
    POSITIONAL ARGUMENTS We cancon gure command line arguments to get input too. They can be requred or options php govcon.php gc:whatTime "F, j Y h:i:s" What is your name? [Human]: It is now July, 31 2017 03:13:26, Human
  • 20.
    ARGUMENTS, CONFIGURE() use SymfonyComponentConsoleInputInputArgument; protectedfunction configure() { $this->setName('gc:whatTime') ->setDescription('Display the current time.') ->addArgument('date_format', InputArgument::REQUIRED, 'Date format') ; }
  • 21.
    ARGUMENTS, EXECUTE() $format =$input->getArgument('date_format'); $now = new DateTime(); $output->writeln('It is now ' . $now->format($format) . ', ' . $name);
  • 22.
    SWITCHES AKA COMMANDOPTIONS Another way to get user input. app$ php govcon.php gc:whatTime --format "H:i:s" What is your name? [Human]: It is now 15:19:31, Human
  • 23.
    OPTIONS, CONFIGURE use SymfonyComponentConsoleInputInputOption;// earlier // ... $this->setName('gc:whatTime') ->setDescription('Display the current time.') ->setHelp('Print the current time to STDOUT') ->addOption('format', 'f', // shorcut InputOption::VALUE_OPTIONAL, 'Date format' ) ;
  • 24.
    OPTIONS, EXECUTE $format =$input->getOption('format') ?? 'g:i a'; $now = new DateTime(); $output->writeln( 'It is now ' . $now->format($format) . ', ' . $name );
  • 25.
    CHOICES AND QUESTIONS Finally,you can let users choose from one or more options. app$ php govcon.php gc:whatTime --format "H:i:s" Which format do you prefer? [military] H:i [common ] g:i a > military It is now 15:27
  • 26.
    CHOICE, EXECUTE $choices =[ 'military' => 'H:i', 'common' => 'g:i a', ]; $question = new ChoiceQuestion( '<question>Which format do you prefer?</question> ', $choices ); $helper = $this->getHelper('question'); $format = $helper->ask($input, $output, $question); $now = new DateTime(); $output->writeln('It is now ' . $now->format($choices[$format]));
  • 27.
    CONFIGURATION FILES Use acon guration le to store credentials, or persist values between runs. ->addOption('config', null InputOption::VALUE_REQUIRED, 'Configuration Files' ); $file = $input->getOption('config') ?? '~/govcon.config.php'; $values = $this->readConfig($file); // parse it
  • 28.
    The format foryour config file can be whatever you can parse that is easily writeable by your users. It can be a simple ini file, YAML, json, xml, etc. CREATING A .PHAR
  • 29.
    A WHAT? A Phararchive is used to distribute a complete PHP application or library in a single le. For the command line, a phar file gives us away to distribute ALL our files inside a single file. The PHP interpreter knows how to work with it. You’re probably already familiar with at least one phar - composer.phar
  • 30.
    BUILD SKELETON There aremultiple ways to build one, I’ll show you how I do it. See also packaging Your Apps with Phar
  • 31.
    BUILD-PHAR.PHP <?php define ('BASE_SRC', realpath(__DIR__. '/../app') . '/'); $name = 'govcon.phar'; $app = 'govcon.php'; if (file_exists($name)) { unlink($name); } $phar = new Phar($name, 0, $name); $phar->setSignatureAlgorithm(Phar::SHA1); // add everything under our APP dir $it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(BASE_SRC, FilesystemIterator::SKIP_DOTS) ); $phar->buildFromIterator($it, BASE_SRC); Explain what this does
  • 32.
    HAVE YOURSELF APHAR Once the build comopletes, you’ll have a govcon.phar le. build$ ./govcon.phar gc:whatTime Which format do you prefer? [military] H:i [common ] g:i a > military It is now 15:40 You can share this file with others. Place it somewhere in there $PATH and it’ll be available globally.
  • 33.
    WORKING WITH FILES Inyour app le get the path to resources/ and inject it to any comand which needs it. $resource_dir = __DIR__ . '/resources'; $application->add(new FooSomeCommand($resource_dir)); Use DirectoryIterator not glob() to get list of les // copy over resources for this column // phar doesn't support glob $files = new DirectoryIterator($this->resource_dir . "/myfiles/"); foreach ($files as $file) { } If you’re copying files from inside your archive to the user’s file system, use the DirectoryIterorator.
  • 34.
    THIRD PARTY LIBRARIES Notall libraries may work with bundled les in a phar. In my case, I couldn’t bundle some font files for the Intervention library to use for image manipulation.
  • 35.
    SECURITY If you needto use api_keys or credentials, don’t put them in your phar le. Use a con guration le and make sure its locked down so only the current user can read it.
  • 36.
    CONCLUSION Automation can saveyou time and prevent errors. Symfony Console handles a lot of the plumbing so you can focus on your use case.
  • 37.
    THANK YOU @omerida I publishphp[architect], a monthly magazine for PHP developers. Check it out: www.phparch.com php[world] is our fall conference in Tyson’s Corner. world.phparch.com