Posts Tagged development

Revisiting PHP Command-Line Scripting

A few years ago I blogged about “Using Silex for Console Tasks”, but times have changed and so have I. I still use Silex for projects, but when it comes to console commands, I’ve found a new toy. You see, the Symfony Console Component can be a bit cumbersome – it groups your console commands as though they were all a single application; but, when I’m writing a script or a system utility, I don’t need an entire application – I just need one command, with one set of arguments, and one help screen.

php-GetOptionKit, from github user c9s, is a very simple library that has many of the features I’ve wished for in a PHP CLI toolkit – and, as a bonus, it’s very fast to get wired up using Composer.

The Bootstrap

OK, so, let’s do a quick walk-through for bootstrapping a project using Composer and GetOptionKit.

First, create a folder for your project. Let’s call this one “getoptionkit-skeleton”.

$> mkdir getoptionkit-skeleton
$> cd getoptionkit-skeleton

Inside that folder, get composer from http://www.getcomposer.org (or you can follow the “global installation” instructions to store it outside the folder).

curl -s https://getcomposer.org/installer | php

As a security-conscious software developer, you should immediately recognize that the above command is dangerous; it’s quite literally “Remote/Arbitrary Code Execution”.

Nevertheless, folks seem to be confident in the security of the composer system, so for the sake of the tutorial we will forge ahead.

Once composer is in place, we must define the composer.json for our project. Begin by executing this command:

$> composer init

This will begin the interactive composer configuration process:

Welcome to the Composer config generator

This command will guide you through creating your composer.json config.

Package name (/) [root/getoptionkit-skeleton]: beryllium/getoptionkit-skeleton
Description []: A quick start package for creating CLI scripts using GetOptionKit
Author [Kevin Boyd ]: (I pressed enter to accept the default)
Minimum Stability []: dev

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]? (I pressed enter to say yes)

Composer then asks you to search for a package. I typed “c9s/getoptionkit” and waited a bit for the result. I chose the option for “dev-master”, because it’ll be the latest and greatest (or brokenest. We shall see.)

I said “no” for defining development dependencies, and then said “yes” to generate the composer.json file:

{
  "name": "beryllium/getoptionkit-skeleton",
  "description": "A quick start package for creating CLI scripts using GetOptionKit",
  "require": {
    "c9s/getoptionkit": "dev-master"
  },
  "authors": [
    {
      "name": "Kevin Boyd",
      "email": "my.example.email@example.com"
    }
  ],
  "minimum-stability": "dev"
}

At this point, I typed “composer install” to install my vendor dependencies.

Now it’s time to create the bootstrap.php file:

<?php

if (php_sapi_name() != 'cli') {
die('Must run from command line');
}

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 1);
ini_set('log_errors', 0);
ini_set('html_errors', 0);

require_once __DIR__ . '/vendor/autoload.php';

function writeln( $msg = '' )
{
echo $msg . "\n";
}

I like to define the writeln() function as a quick shortcut, you could just as easily leave it out or call it something else.

The Fun Part

Now we can begin creating our command line program. I’m going to build a simple script called “bin/hello” with a few features to give GetOptionKit a workout.

Naturally, at this point you should make a “bin” directory and begin editing a file inside it called “hello”. Remember to chmod a+x bin/hello, to make it easy to execute.

#!/usr/bin/env php
<?php
require_once __DIR__ . '/../bootstrap.php';
$app = new GetOptionKit\GetOptionKit();
$app->add( 'h|help', 'Display usage instructions' ); // The '|' allows you to define a short and a long form: -h and --help, in this case
$app->add( 'g|greeting:', 'Greeting (default: "Hello")' );

$options = $app->parse( $argv );
$arguments = $options->getArguments();
array_shift($arguments); //Strip off the command name

if ( $options->has( 'help' ) || count( $arguments ) != 1 )
{
  writeln( 'Usage: hello [-g "Greetings"] yourname' );
  writeln();
  $app->printOptions();
  writeln();
  exit(0);
}

//Set the default greeting
$greeting = 'Hello';

//Check for the optional override
if ( $options->has('greeting') )
{
  $greeting = $options['greeting']->value;
}

writeln( $greeting . ', ' . $arguments[0] . '!' );

Now at this point, the script can be flagged as executable (chmod a+x bin/hello) and launched. You should see the following:

$> bin/hello
Usage: hello [-g "Greetings"] yourname

    -h, --help Display usage instructions
    -g, --greeting  Greeting (default: "Hello")

And now you can give it a spin:

$> bin/hello world
Hello, world!
$> bin/hello -g "G'day" world
G'day, world!
$> bin/hello -g world
Usage: hello [-g "Greetings"] yourname

-h, --help Display usage instructions
-g, --greeting  Greeting (default: "Hello")

You might wonder why the last one failed – that’s because the conditions checked in bin/hello didn’t evaluate to true. After parsing for the value of the “greeting” option, there weren’t enough arguments left to define “yourname”, so the script aborted and showed the help instead. You could set up the evaluation logic in whichever way suits your workflow, of course.

I like GetOptionKit because it lets me quickly spin up a command-line script that behaves similarly to the Unix/Linux/Gnu/BSD utilities I’m accustomed to.

If you’re wanting a shortcut to get this up and running even faster, now you can use my skeleton:

$> composer create-project beryllium/getoptionkit-skeleton myproject

Notes:

  • In order to get the getoptionkit-skeleton working as an installable composer package, I had to modify my composer.json and change the dev-master reference to “1.*” and remove the “minimum-stability” line and preceding comma. I also had to push a tag called 1.0.0 to my github repository.
  • If you are interested in a recent article about using Symfony Console Component, I recommend this post from dcousineau.

, , , , , ,

No Comments

Mailstrom email tool

There comes a time in every developer’s life when he/she is simultaneously bored and annoyed at something.

Usually it’s on a day ending in “y”.

Anyway, the thing that piqued my annoyance this afternoon was Amazon SES and all the hacks I’ve had to do in the past to get sendmail/postfix to relay via it. They are both very much the wrong tool to use for that, but due to various business requirements, I did what I had to do to get them to work.

But, I always felt uneasy about it, especially since it seemed to create more of a problem than it solved. But now, I have created the first part of a solution to that problem: Mailstrom.

Mailstrom is a quick-and-simple command-line PHP script that allows users to send email via Amazon SES. It gives two ways of doing this: As an option on the command line, or by piping the message body to the command. The Readme has installation, configuration, and usage instructions.

Mailstrom was built using Composer, Amazon AWS SDK for PHP, and php-cli-tools from James Logsdon.

My future plans for it include raw message support, as well as Amazon SNS support. I might also consider a queuing/spooling system using SQS, but that would be a very long-term idea since I don’t have a viable use-case for that complexity at the moment.

In the meantime, it works as a simple tool that gets the job done. And that’s the best way to eliminate annoyance.

, , , , ,

No Comments

PHP Annotations

Annotations are all the rage in some PHP circles these days, but I don’t like them. I try to avoid them as much as possible, and I hope that my reasoning for doing so is justified.

What are annotations?

Annotations are a form of meta-code; in practice, they allow you to modify attributes and behaviors of your methods and classes in fast and convenient ways. Three common uses for annotations in symfony2 are routing, validation, and ORM binding – although regular users of PHPUnit will also be familiar with annotations for data providers and test grouping.

Why are they awesome?

Annotations do have some good qualities. They allow you to reduce the clutter of your projects by folding various configuration and behavioural information into the class itself. For example, the average symfony2 bundle has a validation.yml and routing.yml file – both of these files can be eliminated or reduced in complexity by using annotations.

Additionally, annotations allow you to design and maintain your projects in a more straightforward manner. Instead of having to open one class file and a handful of configuration files (or a handful of supporting classes), all the information is right there within the primary class file.

You might be interested in this article on how to build custom PHP annotations, to see some of the power and flexibility they can bring to your projects.

Why am I avoiding them?

I must confess that I have a semi-irrational paranoia about using annotations. However, I feel that there are also some rational reasons to consider avoiding them.

One such reason is that the decentralization of certain types of configuration can lead to oversights and possible repetition of unanticipated mistakes, which can cause behavior that is unpredictable and hard to catch and debug. While the term “decentralization” might seem out of place when talking about merging code and configuration together, it helps to remember that a symfony2 bundle (or any PHP project) can have many controllers and classes and entities. You might end up with two controllers fighting over routing (an easy-to-solve issue, I’m sure), or you might find yourself struggling to remember a route name and which controller handles it (a situation where a single routing definition can help your code be more self-documenting).

There are also some speed concerns with annotations, due to the heavy use of Reflection, but they are mostly alleviated through the proper use of caching.

Most importantly, I feel that it is wrong to have what amounts to program logic tucked away in what otherwise looks like simple comments. If PHP had an official non-comment-like notation for annotations, I might be more inclined to use them. As it stands, I only use annotations in unit tests at the moment – not in the main/production code.

That’s pretty much the crux of what I don’t like about them: I do not like using comments as code.

How about you?

, , , ,

2 Comments

On Phar: PHP Archive Files

As part of an internal project at work, I built a command-line script that performed a series of useful actions. However, it was all contained within one big file to make it easy to move around to ~/bin folders and such. I found that this was difficult to maintain, and I wanted to be able to separate it into smaller components – but this would break the ability to move it around at will. I needed the best of both worlds.

Enter: Phar

PHP Archive (PHAR) files solve this problem. They allow you to bundle a bunch of associated source code files together into a single virtual filesystem. That’s exactly what I needed. Of course, most languages would call this a compiler … but most languages aren’t as fun as PHP. And Phar is not a real compiler, anyway – it’s more of a collector :)

To create the simplest of Phar projects, I made a basic script to practice Phar usage:

# ./src/hello.php

<?php
$input = readline( "What is your name? " );
echo "Hello, " . $input . "\n";
?>

Then, I began writing the build script:

# ./build.php

<?php
$p = new Phar( 'hello.phar', 0, 'hello.phar' );
$p->buildFromDirectory('src/','./\.php$/');

$stub = file_get_contents( 'stub.php' );

$p->setStub( $stub );
?>

The next step is to create a stub file. The stub is used to wrap the files inside the phar, and begin running the program.

# ./stub.php

<?php
Phar::mapPhar();
include 'phar://hello.phar/hello.php';
__HALT_COMPILER();
?>

At this point, I tried running my build script (php ./build.php), but I found out that I had made a few oversights:

  1. In order for a Phar file to be modified, you need to set “phar.readonly” to 0 in the php.ini on your development machine. This is a security feature to prevent Phar files from having the ability to behave like trojan horses and viruses; you might want to consider only enabling writable Phars in specific, controlled situations (i.e., during the build process only)
  2. It lacked the “hashbang” required to make it executable on Linux. Adding “#!/path/to/php” to the top of the stub file fixes this. Alternatively, you can use “php hello.phar” to avoid having to do this.
  3. It wasn’t marked as executable. Fixing the hashbang issue and running a simple “chmod 744 hello.phar” after running “php build.php” fixes this.

Once I sorted those issues out, I ended up with the desired hello.phar file.

Phar Trek: The Next Generation

My next avenue of Phar research is to find out the ideal way to make an application with multiple files come together as a single unit. This post is just a stepping-stone toward that goal. I’m going to have to look into the best way to achieve that functionality – with a particular eye to coding projects in a way where the ability to convert to a Phar is transparent (e.g., it can function both inside a Phar and outside of one – and includes a stable build process).

Additional Phar Resources

That’s all I know of so far. If I find any new resources, I’ll update this list.

, , , , ,

No Comments