Nice PHP console application
Sometime you need to create command line interface (CLI) for your application and when you want to have it nicely formated, colorful or you want to have a table there it is not easy to do it. You can look on internet and find out, how to do such a thing, but you would have to write a lot of code.
Symfony console
With Symfony console you can do whatever you are used to see when working with console. E. g. tables, progress bars, questions, dialogs, etc. And it will help you with lot of other common things. It is very easy to add required or optional argument, create help or add another command.
New problem – Anagram
I want to continue on my previous article [What is and how to use Composer]. There was an example project where you have to solve if given string is palindrome or not. Now, we can expand it with new problem.
An anagram is a type of word play, the result of rearranging the letters of a word or phrase to produce a new word or phrase, using all the original letters exactly once.
- Wikipedia
For example silent – listen, admirer – married, funeral – real fun. The solution involves a CLI where I can easily test if words are anagrams or palindromes. Of course there has to be help to each command and a hint how to use it.
Step by step
You can download the project from GitHub commit or go through mentioned article and create it yourself. You should end up with directory structure like this :
src
Palindrome.php
tests
PalindromeTest.php
vendor
…
composer.json
composer.lock
Again start with the tests. So create file AnagramTest.php in src directory. The class name will be Anagram
and method might be isAnagram
. So you have to test an real anagram and some other string which is not an anagram.
use src\Anagram;
class AnagramTest extends PHPUnit_Framework_TestCase {
private $anagram;
public function setUp() {
$this->anagram = new Anagram();
}
public function testSuitableString() {
$this->assertTrue($this->anagram->isAnagram('silent', 'listen'));
$this->assertTrue($this->anagram->isAnagram('I am Lord Voldemort', 'Tom Marvolo Riddle'));
}
public function testNotSuitableString() {
$this->assertFalse($this->anagram->isAnagram('anagram', 'palindrome'));
}
}
Anagram
There are many ways how to check if strings are anagram. One of them is to sort both strings and compare them. If they are equal they are anagram. To be sure, you can delete spaces and make the strings lowercase.
namespace src;
class Anagram {
/**
*
* @param string $str1
* @param string $str2
* @return boolean
*/
public function isAnagram( $str1, $str2 ) {
return $this->sortString($this->sanitizeString($str1)) === $this->sortString($this->sanitizeString($str2));
}
/**
* Removes all spaces and transorms letters to lowercase.
* @param string $str
* @return string
*/
private function sanitizeString( $str ) {
return \strtolower(\str_replace(' ', '', $str));
}
/**
* Sort characters and return them.
* @param string $str
* @return string
*/
private function sortString( $str ) {
$chars = str_split($str);
sort($chars);
return implode('', $chars);
}
}
Now run the test phpunit --bootstrap ./vendor/autoload.php ./tests/AnagramTest.php. The result should be OK (2 tests, 3 assertions)
and you may continue to create the CLI.
Creating command line interface
First you need to install Symfony Console package. Open the composer.json file and add new dependency into the require
key and run composer install command.
...
"require": {
"phpunit/phpunit": "^4.6.0",
"symfony/console": "~2.6"
},
...
Now, when you have everything what is needed, create bin folder in the root directory. There will be the whole console application located. In the final, you run only one main script, which takes the command name and arguments and processes it.
To have some order in it, let’s create it like this :
bin
command
AnagramCommand.php
PalindromeCommand.php
console.php
src
…
tests
…
vendor
…
composer.json
composer.lock
Because there is new folder ( new namespace to be mapped ) you have to add this folder into autoload
and rebuild the autoload.php with command composer dump-autoload.
...
"autoload": {
"psr-4": {
"src\\" : "src",
"bin\\": "bin"
}
}
...
AnagramCommand.php
Now is the time to create the first command and it is done by extending Command
class from Symfony\Component\Console\Command
namespace. Then you have to set some basic configuration like the name, description, arguments ( if has any ) and so on. There is a method for this which you have to override – protected function configure()
.
namespace bin\command;
use Symfony\Component\Console\Command\Command;
class AnagramCommand extends Command {
protected function configure() {
$this
->setName('anagram')
->setDescription('Checks whether two strings are anagrams.')
->addArgument('word', InputArgument::REQUIRED, 'The main word')
->addArgument('anagram', InputArgument::REQUIRED, 'Word to be checked if it is anagram with the main one')
->setHelp("An anagram is a type of word play, the result of rearranging the letters of a word or phrase to produce a new word or phrase, using all the original letters exactly once.\nLink : https://en.wikipedia.org/wiki/Anagram")
;
}
}
The setName
part is clear I think. Description is short text displayed in list of available commands as a hint unlike help which is displayed on demand by typing --help
option. The command takes two words / strings as arguments to determine whether they are anagrams or not. I named it word and anagram, make it required with description ‘The main word’ and ‘Word to be checked if it is anagram with the main one’.
Method protected function execute($input, $output)
is where everything is done. You get the arguments values from the $input
object and write back to console through the $output
object.
namespace bin\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use src\Anagram;
class AnagramCommand extends Command {
...
protected function execute(InputInterface $input, OutputInterface $output) {
$word = $input->getArgument('word');
$anagramWord = $input->getArgument('anagram');
$anagram = new Anagram();
if( $anagram->isAnagram($word, $anagramWord) ) {
$output->writeln('These strings are anagrams!');
} else {
$output->writeln('These strings are not anagrams!');
}
}
}
PalindromeCommand.php
Here again, you set up the whole command and create the execute part. Here it is a little bit easier, because it takes only one argument and tests it.
namespace bin\command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputArgument;
use src\Palindrome;
class PalindromeCommand extends Command {
protected function configure() {
$this
->setName('palindrome')
->setDescription('Checks if string is palindrome or not')
->addArgument('string', InputArgument::REQUIRED, 'String to by checked')
->setHelp("A palindrome is a word, phrase, number, or other sequence of characters which reads the same backward or forward.\nLink : https://en.wikipedia.org/wiki/Palindrome")
;
}
protected function execute(InputInterface $input, OutputInterface $output) {
$string = $input->getArgument('string');
$palindrome = new Palindrome();
if( $palindrome->check($string) ) {
$output->writeln('This string is palindrome!');
} else {
$output->writeln('This string is not palindrome!');
}
}
}
console.php
Application’s main file where you put together all parts along with autoloader. That means create Application
object, add commands and run it.
namespace bin;
$container = require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Console\Application;
$console = new Application('First Symfony Console Application', '0.1.0');
$console->addCommands(array(
new \bin\command\AnagramCommand(),
new \bin\command\PalindromeCommand()
));
$console->run();
Testing the application
Everything is ready and you can try it. Just run the command php console.php and you should see nice output with information about the app name, usage, options and available commands.
To test palindrome, type php console.php palindrome "put it up" and it should end with ‘This string is palindrome!’. Or if you don’t know what is the anagram command from available commands, type php console.php anagram --help and it gives you all information you need.
Conclusion
There are lot of other things it is capable of. Try to run the anagram command ( php console.php anagram ) without any argument and it alerts red exception with ‘Not enough arguments (missing: “word, anagram”).’ message. If a command does’t need an argument, set it to InputArgument::OPTIONAL
, or if it should have options ( like -h / –help ) use method addOption(...)
.
Maybe next time I write a post about using tables and progress bars if I find appropriate usage.
You can find the whole project on GitHub.
References
The Console Component introduction on official Symfony website.
Do you like this post? Is it helpful? I am always learning and trying new technologies, processes and approaches. When I struggle with something and finally manage to solve it, I share my experience. If you want to support me, please use button below. If you have any questions or comments, please reach me via email juffalow@juffalow.com.
I am also available as a mentor if you need help with your architecture, engineering team or if you are looking for an experienced person to validate your thoughts.