9

I am writing a small command line application in php.

What is the correct way to handle command line arguments and options?

There seems to be the argv array, $_SERVER['argv'], and getopt but its confusing when to use each?

Also with regards to options i.e. "argument --option" what is the best way to get these?

2
  • 1
    getopt() works well. Have you tried using it? Commented Aug 20, 2012 at 19:23
  • I wondered this a while back, and I wrote a library (shameless plug) to handle parsing different types of command line arguments in PHP. The project is on on GitHub. I actually used it at work for a while and it worked well, though I do think it still lacks some features like executing callbacks when certain arguments are present. If you do use it, suggestions are welcome. Commented Jun 17, 2013 at 8:28

2 Answers 2

10

Arguments, made easy

One day I decided to defeat this monster once and for all. I forged a secret weapon - a function that acts as a storage, a parser and a query function for arguments.

    //  define your arguments with a multiline string, like this:

    arg('

            -q      --quiet-mode        bool        Suppress all error messages
            -f      --filename          str         Name of your input document
            -m      --max-lines         int         Maximum number of lines to read

    ');

    $argText = arg("??"); // removes bool/int/etc column
    print "
        Syntax:
            $argText
    
    ";

    //  then read any value like this:

    $maxLines = arg("max-lines",99);
    $filename = arg("filename");
    $stfuMode = arg("quiet-mode");


    //  let's see what we got

    printf("Value of --max-lines: %s\n",$maxLines);
    printf("Value of --quiet-mode: %s\n",$stfuMode);
    printf("Value of first unnamed argument: %s\n",arg(1));

Explanation

  • All you need to do is write the argument list as a multiline string. Four columns, looks like a help, but arg() parses your lines and finds out the arguments automatically.

  • Separate columns by two or more spaces - just like you would anyway.

  • Once parsed, each item will be represented by an array of fields, named char, word, type and help, respectively. If there's no short (char) or long (word) version for a parameter, just use a dash. Not for both, obviously.

  • Types are what they seem: bool means there's no value after the parameter; it's false if missing, true if present. The int and str types mean there must be a value, and int makes sure it's an integer. Optional parameters are not supported. Values can be separated by space or equal sign (i.e. "-a=4" or "-a 4")

  • After this first call, you have all your arguments neatly organized in a structure (dump it, you'll see) and you can query their values by name or number.

  • Function arg() has a second parameter for defaults so you'll never have to worry about missing values.

  • With the argument "??", as you see in the example, you get back a string that your command line tool can use as a help with arguments. Gone are the days when you had to keep those two in sync.

The arg() function itself


    function arg($x="",$default=null) {

        static $argtext = "";
        static $arginfo = [];

        /* helper */ $contains = function($h,$n) {return (false!==strpos($h,$n));};
        /* helper */ $valuesOf = function($s) {return explode(",",$s);};

        //  called with a multiline string --> parse arguments
        if($contains($x,"\n")) {

            //  parse multiline text input
            $argtext = $x;
            $args = $GLOBALS["argv"] ?: [];
            $rows = preg_split('/\s*\n\s*/',trim($x));
            $data = $valuesOf("char,word,type,help");
            foreach($rows as $row) {
                list($char,$word,$type,$help) = preg_split('/\s\s+/',$row);
                $char = trim($char,"-");
                $word = trim($word,"-");
                $key  = $word ?: $char ?: ""; if($key==="") continue;
                $arginfo[$key] = compact($data);
                $arginfo[$key]["value"] = null;
            }

            $nr = 0;
            while($args) {

                $x = array_shift($args); if($x[0]<>"-") {$arginfo[$nr++]["value"]=$x;continue;}
                $x = ltrim($x,"-");
                $v = null; if($contains($x,"=")) list($x,$v) = explode("=",$x,2);
                $k = "";foreach($arginfo as $k=>$arg) if(($arg["char"]==$x)||($arg["word"]==$x)) break;
                $t = $arginfo[$k]["type"];
                switch($t) {
                    case "bool" : $v = true; break;
                    case "str"  : if(is_null($v)) $v = array_shift($args); break;
                    case "int"  : if(is_null($v)) $v = array_shift($args); $v = intval($v); break;
                }
                $arginfo[$k]["value"] = $v;

            }

            return $arginfo;

        }

        if($x==="??") {
            $help = preg_replace('/\s(bool|int|str)\s+/'," ",$argtext);
            return $help;
        }

        //  called with a question --> read argument value
        if($x==="") return $arginfo;
        if(isset($arginfo[$x]["value"])) return $arginfo[$x]["value"];
        return $default;

    }

I hope this helps a lot of lost souls out there, like I was. May this little function shed a light upon the beauty of not having to write a help AND a parser and keeping them in sync... Also, once parsed, this approach is lightning fast since it caches the variables so you can call it as many times as you want. It acts like a superglobal.

Also available on my GitHub Gist.

Sign up to request clarification or add additional context in comments.

4 Comments

I can't believe this amazing piece of ingenuity hadn't yet been upvoted! Take your upvote, this is super useful. Thanks for contributing it. There's an error here tho, you need to switch $word/$char on this line, like so: $key = $char ?: $word ?: ""; if($key==="") continue;
@scp First of all, thank you for the upvote and especially the praise! I'm really proud of this one, helped me a lot and now that you mentioned, I'll put it on Github Gist. Now about the word/char question: I keep wondering what the correct order would be. Do you think it's better to check short options first?
Ahh please ignore that comment about the error - there was no error, I was sleep deprived! Yes, a gist would be great. As for order, personally I don't think it's too important.
I think it's gonna work either way. But I'm always open to improvement suggestions. I created the Gist and updated the article a bit so now the link is at the bottom. Have fun! And thanks again!
7

You can retrieve the "raw" arguments using $argv. See also: http://www.php.net/manual/de/reserved.variables.argv.php

Example: php file.php a b c

$argv will contain "file.php", "a", "b" and "c".

Use getopts to get the parameters "parsed", PHP will do the dirty job for you. So it's probably the best way to go in your case as you want to pass the parameters with --options. Have a close look at http://www.php.net/manual/de/function.getopt.php It describes the function well.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.