Skip to end of metadata
Go to start of metadata

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[

Zend Framework: Zend_Console_Getopt Component Proposal

Proposed Component Name Zend_Console_Getopt
Developer Notes
Proposers Bill Karwin
Revision 0.1 - Draft proposal (wiki revision: 10)

Table of Contents

1. Overview

Zend_Console_Getopt is an object-oriented component to parse command-line options for PHP scripts. This is not typically required for web applications, but it can be useful for scripts called at the command-line.

2. References

3. Component Requirements, Constraints, and Acceptance Criteria

  • Support short, single-letter options, e.g. "-a".
  • Support clusters of short options, e.g. "-abc".
  • Support long, multi-letter options, e.g. "--apple".
  • Support parameters for options, e.g. "-a param".
  • Support simple validation of parameters as words or integers.
  • Support parameters as separate argument or combines with option, e.g. "--apple=param".
  • Support option synonyms.
  • Generate usage help message.
  • Specify valid options in a declarative syntax.
  • Support GNU Getopt syntax.
  • Support more extensive syntax to declare synonyms, parameter types, and help messages.
  • Signify end of parsable options with "--" argument.
  • Allow extension to let users define their own syntax parser.

4. Dependencies on Other Framework Components

  • Zend_Exception

5. Theory of Operation

Provide an object-oriented interface to allow application developers to specify options for a given application, and arguments from the command-line of the current invocation of the application. Presence and value of individual options are provided as magic object attributes, e.g. "$opts->optionname".

The result of this attribute is the value of the option's parameter, if any. Otherwise a true value is returned if the option takes no parameter but was given on the command-line.

Specifying options is done with a declarative syntax that defines one or more synonyms for an option, any parameters that may be given to the option, and a help string for the option. For example:

This means that --apple and -a are synonyms, and take no parameter. --banana and -b are synonyms, and take no parameter. --pear and -p are synonyms, and require (=) a string (s) parameter. Each of these rules is a key in an associative array, the values of which are the help strings for the respective option.

Any of the synonyms can be used as the magic object attributes. "$opts->apple" and "$opts->a" return the same result.

A shorter declarative syntax compatible with GNU getopt is supported. The GNU getopt short syntax supports only single-letter option names, only required string parameters, and no support for declaring help strings (though help strings can be added).

The object generates a usage text message, formatted for text output in case the user gives incorrect option arguments, or requests the usage help for reference. Help messages can be added to the GNU getopt short syntax with the addHelp() method (see use case later in this proposal).

Resolving the parsing of command-line arguments and validating them against the option rules declaration is done in a "lazy" fashion. That is, the parsing is not done until an object attribute is requested, or alternatively when the object method parse() is called directly. The lazy parsing step allows the application programmer to add more option rules, more arguments, and more help strings before the parsing is done (the parsing may generate exceptions, so it's useful to delay it until all rules, argument data, and help strings have been established).

6. Milestones / Tasks

Milestone 1: Working prototype checked into incubator
Milestone 2: Community & Zend Core team review
Milestone 3: Code & test review
Milestone 4: Write full documentation
Milestone 5: Approve and move to core library

7. Class Index

  • Zend_Console_Getopt
  • Zend_Console_Getopt_Exception

8. Use Cases

In these use-cases, an artificial ARGV array is passed to the constructor of Zend_Console_Getopt. In practice, the arguments come from the script's command-line argument array.

Parsing short options

Parsing long options

Handling user errors

Notice in the above example that querying the 'apple' option caused all options to be parsed, thus generating the exception regarding the incorrect usage of the 'pear' option.

Adding option rules

Adding arguments

Adding help messages and getting the usage message

Adding synonyms (aliases)

Dumping options as a string

Dumping options as Json

Dumping options as XML

9. Class Skeletons



Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Nov 25, 2006

    <p>Nice one,</p>

    <p>I gonna using it, I've started to write a lite administration console (running on lighthttpd as root) using zend frameworking to manage a development server. So yes it's a good thing to have in the framework. An other use case could be in Zend_Image (if it came up to life) to run imagemagick...</p>

    1. Nov 28, 2006

      <p>Great, I'm glad the Getopt class will be useful to you Laurent.</p>

      <p>As for Zend_Image, there have been a couple of proposals for a Zend_Image class, but in my opinion neither has demonstrated a compelling use case that shows why a new component is needed, instead of simply using the existing PHP interfaces to ImageMagick or GD. You might want to read the proposal currently in the 'New' section and follow up there for discussions of the image subject. </p>

  2. Nov 26, 2006

    <p>It is cool but I really miss one feature. I would like to have a possibility to register callbacks for each object. I would suggest to redesign the Zend options array to something like that:</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[

    'a|add' => array(

    'Adding stuff to whatever'.

    array('MyFassadeClass', 'add'),



    <p>Also I would like to have the following methods:</p>
    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $o = new Zend_Console_Getopt;

    // Register a callback
    $o->registerCallback('a|append', array('MyFassade', 'some_function'));

    // Remove a given callback.
    $o->removeCallback(string $optionString|Callback $callback);

    // Adding a global callback for every found option. It passes the option
    // as an argument
    $o->registerGlobalCallback(array('MyFassade', 'process'));]]></ac:plain-text-body></ac:macro>

    1. Nov 26, 2006

      <p>I'm not sure I understand how this would work. Can you supply some use cases?</p>

      1. Nov 26, 2006

        <p>In generic terms, he wants getopts to handle running the proper function/class as well.</p>

        <p>You would then have to call something like $o->run() with would process the opt's and run the callbacks registered with each opt.</p>

    2. Nov 28, 2006

      <p>Hi Lars,</p>

      <p>I understand the use case, but I had in mind that the Getopt class would be simpler. It parses and validates options, and provides an interface to the developer to know which options were specified. Getopt leaves the interpretation and execution of code based on those options to the developer, like most other implementations of Getopt-like functionality in other languages.</p>

  3. Nov 26, 2006

    <p>Some comments:</p>

    <ul class="alternate">
    <li>What kind of configuration options could you supply as the third argument of the constructor?</li>
    <li>Is the order of the constructor arguments the best? I think the first and third options would be used more often than the second. Maybe swap the second and third.</li>
    <li>I know this is based on Getopt, but would something like Zend_Console_Options or Zend_Console_Args be a more intuitive name? Well, that's a judgment call.</li>
    <li>Instead of dumpString(), dumpJson(), dumpXml(), I think toString(), toJson(), and toXml() are better. Then dumpOptions() could just be dump().</li>
    <li>Instead of 3 addMode* methods, addMode() should take a constant as its second argument</li>
    <li>I don't understand the purpose of parseLongOption(), parseShortOptionCluster(), parseSingleOption(), and getRemainingArgs(). I mean, I understand what they do, but not why they're necessary.</li>

    1. Nov 28, 2006

      <p>Hi Matthew,</p>

      <p>The currently available configuration options can be specified by the following constants:</p>
      <ul class="alternate">
      <li>Zend_Console_Getopt::CONFIG_RULEMODE; either 'zend' or 'gnu', but in a way this is redundant because the class can infer which mode based on whether the rule spec is an array (for zend mode) or a scalar (for gnu mode). This config option is more interesting if additional rule spec syntax modes are introduced.</li>
      <li>Zend_Console_Getopt::CONFIG_DASHDASH; default true, controls support of '-' option terminator. Arguments following '-' are treated as non-option arguments.</li>
      <li>Zend_Console_Getopt::CONFIG_IGNORECASE; default false, controls whether '-option' and '-OPTION' are treated the same.</li>

      <p>The order of constructor arguments is always tricky.</p>

      <p>I thought a name including the word 'Getopt' would be familiar enough to developers who are used to similar functionality being called getopt or getopts in other languages.</p>

      <p>The suggestion regarding toString(), toJson(), toXml() is a good suggestion.</p>

      <p>The functions parseLongOption(), parseShortOptionCluster(), and parseSingleOption() are <code>protected</code> functions. They are supposed to be called only internally to the class during the parsing operation.</p>

      <p>The function getRemainingArgs() returns the arguments that are non-option arguments. For example:</p>

      <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
      ls -l filename

      <p>A call to getRemainingArgs() would return 'filename'.</p>

      <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
      grep -l – '-pattern' filename

      <p>A call to getRemainingArgs() would return '-pattern', 'filename'</p>

      <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
      mysql --user <username> databasename --password <passwd>

      <p>A call to getRemainingArgs() would return 'databasename'</p>

      <p>You can see that there are some cases in which it is nontrivial to separate options and option parameters from non-option arguments. Since the Getopt parser has already had to do that, it can give you the array of non-option arguments.</p>

      1. Nov 28, 2006

        <p>Oh, I meant to say something more about order of constructor arguments.</p>

        <p>It might be that in some cases, you would want to pass config options but not argv. So by changing the order you could do this:</p>
        <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
        $opts = new Zend_Console_Getopt('abp:', array(...config...));

        <p>But there could also be cases in which you would want to pass argv, but the default config options are desired. So you would want to keep it as it is:</p>
        <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
        $opts = new Zend_Console_Getopt('abp:', $argv);

        <p>Anyway, there are methods setArguments() and addArguments():</p>
        <ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
        $opts = new Zend_Console_Getopt('abp:');
        $opts->setArguments($argv); // replaces arguments
        $opts->addArguments($moreArgv); // appends to arguments

        <p>I think there should be a method addConfig() as well, that takes an array of config options just as the constructor does.</p>

        1. Nov 29, 2006

          <p>regarding adding a <code>addConfig()</code> method. Currently, there is no "consistency" in the Zend Framework for setting options. Every component is using it's own way in doing so. I believe every components should use the same naming conventions for setting option(s).</p>

          <p>In <code>Zend_TimeSync</code> I have:</p>

          <p>public static function setOptions($options = array() {}<br />
          public static function setOption($key, $value) {}</p>

          <p>If every component uses the same naming conventions for setting options, there is less api to learn for the end user, and it opens several other possibilities, such as defining options before a component's instance is created (eg bootstrap file).</p>

          1. Nov 30, 2006

            <p>Good idea Andries, I will conform to a convention of setOption/setOptions function names.</p>

        2. Dec 01, 2006

          <p>Thanks for the reply, Bill. That did a good job of answering my questions.</p>

          <p>Re: the constructor order, I just thought it was more likely that someone was going to set configuration options rather than passing in dummy arguments.</p>

          <p>Nice class. It already looks like it has a leg up on PEAR's Console_Getopt package.</p>

  4. Dec 04, 2006

    <p>I must apologize – I accidentally deleted a comment from Matthew Ratzloff. Just hit the wrong link! Funny that Confluence requires no confirmation click before removing a comment, and "Remove" is adjacent to "Reply". And of course there's no undo.</p>

    <p>But I still had Matthew's comment in my email, so here it is:</p>

    <p>"Is it possible for this class to use Zend_Controller_Request_Cli? After all, it would just be an extension of Request_Abstract with some changes made to the 'param' functions, right?"</p>

    <p>Can you describe a use case? I'm not sure what you mean by use Zend_Controller_Request_Cli. Btw, I see a Zend_Controller_Response_Cli, but I do not see a class Zend_Controller_Request_Cli. Are you suggesting a new class? What would this class do?</p>

    <p>I'm sure the case exists for a Zend_Controller_Request_Cli class. But I would not be in favor of Getopt invoking it directly. Instead, Getopt reports which options were given on the command line. Then the application programmer writes a main() method, which queries the Getopt object and based on the results, invokes appropriate parts of the application. </p>

    <p>What I think you're suggesting is similar to Lars' suggestion for callbacks. That's a legitimate application flow pattern for some types of applications, but it's not the only pattern for application flow, so I don't think it should be implemented in Getopt. That would imply that there's some kind of inherent coupling between that type of application architecture and the task of parsing command-line options, which I don't think is a correct coupling.</p>

    1. Dec 04, 2006

      <p>I hadn't thought too much about it, to tell you the truth. <ac:emoticon ac:name="wink" /> But it would simplify modules that have to work with a variety of input methods (for example... a task scheduling module...).</p>

      <p>Taking your first example,</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $argv = array('-a', '-p', 'p_arg');
      $opts = new Zend_Console_Getopt('abp:', $argv);
      echo $opts->a; // returns true
      echo $opts->p; // returns 'p_arg'

      <p>could instead become,</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $request = $this->getRequest();
      $request->setParams(array('-a', '-p', 'p_arg'));
      $opts = new Zend_Console_Getopt('abp:', $request);
      echo $opts->a; // returns true
      echo $opts->p; // returns 'p_arg'

      <p>Normal operation would obviously eliminate the setParams() line that adds dummy values.</p>

      <p>The Zend_Controller_Request_Cli object would perhaps be created in the bootstrap and automatically populated as such:</p>

      <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
      $request = new Zend_Controller_Request_Cli(); // internally sets contents of 'argv'

      <p>This goes along with <a href="">ZF-615</a>, which would permit $request->setControllerName() and $request->setActionName() to be honored if called manually, allowing multi-controller CLI applications.</p>

      <p>One unrelated thought: To remain consistent, command-line applications should be described either as 'CLI' or 'Console', but not both. So perhaps this proposal should become Zend_Cli_Getopt, or Zend_Controller_Response_Cli should become Zend_Controller_Response_Console. To my mind, 'CLI' seems more immediately descriptive and recognizable.</p>


      1. Dec 04, 2006

        <p>Would this not do the same thing:</p>

        <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
        $request = $this->getRequest();
        $request->setParams(array('-a', '-p', 'p_arg')); // or use $argv
        $opts = new Zend_Console_Getopt('abp:', $request->getParams());

        1. Dec 04, 2006

          <p>Oh, of course. <ac:emoticon ac:name="smile" /></p>

          <p>I wonder... would there be an easy way to then set an array back to $request? Or perhaps set Getopt as a getParam()/setParam() handler for Request_Cli? Maybe there's a way for Request_Cli to use Getopt in order to seamlessly parse arguments...</p>

          <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
          $rules = array(
          'apple|a' => 'Apple option',
          'banana|b' => 'Banana option',
          'pear|p=s' => 'Pear option with required parameter'
          $request = new Zend_Controller_Request_Cli();
          print $request->getParam('pear'); // returns 'p_arg'

          <p>In other words, to only pass one object that serves as an argument container instead of two to the various actions.</p>

          <p>Also, what are your thoughts on the naming issue?</p>

          <p>By the way, let me know when I'm giving you <em>TOO</em> much feedback. <ac:emoticon ac:name="wink" /></p>

          1. Dec 04, 2006

            <p>I think this will set the canonical option arguments back to the request object:</p>

            <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
            $rules = array(
            'apple|a' => 'Apple option',
            'banana|b' => 'Banana option',
            'pear|p=s' => 'Pear option with required parameter'
            $opts = new Zend_Console_Getopt($rules); // implicitly gets console argv
            print $request->getParam('pear'); // returns 'p_arg' (if that option was given)

            <p>That doesn't seem too painful.</p>

            <p>Regarding the naming convention, I usually try to avoid a TLA where a real word will work. So I'd prefer Zend_Controller_Request_Console to remain consistent. But I'm not seeing a need to create a new subclass, so far; we can do it with the existing request class.</p>

            1. Dec 04, 2006

              <p>I understand about avoid TLAs. That means that the existing Zend_Controller_Response_Cli should be changed to Zend_Controller_Response_Console. (No other class references it.)</p>

              <p>As far as the extra subclass, Zend_Controller_Response_Cli extends Abstract and only adds one extra method (__toString()). I think it's more about accurate categorization and room to grow than current functionality in this case. Seems strange to use Zend_Controller_Request_Http for a console application, and there may be CLI-specific functionality added in the future (or from the start--the integration with Zend_Console_Getopt I mentioned above).</p>

              <p>I'll probably create a proposal for this, just to have them all in one place.</p>

              1. Jan 15, 2007

                <p>My intention with Zend_Controller_Request_Cli is that it would use Zend_Console_Getopt once it was present. As Bill noted, using toArray() to populate setParams() is trivial, and my idea is that this would happen on the first call to getParam|s() or in the constructor.</p>

                <p>As for renaming, I agree that calling it Zend_Controller_Request_Console makes sense, to keep the relation between them clear.</p>

  5. Dec 28, 2006

    <p>I know that this is already under review but I was wondering if there is a way to add dependency to arguments, meaning that if you have -a and -b linked then they both must be present in $argv or an error is given.</p>

    <p>I'm currently writing a command line network management wrapper that will use about 60 different arguments and many of the arguments must be linked. It's been a real bear to get it and this is the best solution I've seen so far.</p>

    1. Dec 28, 2006

      <p>There is no syntax currently in Zend_Console_Getopt to specify interdependency between options. I don't have an idea for how this could be added to the declarative syntax without making it complex. Any solution needs to allow for the more common case (no interdependencies) to remain simple to use.</p>

      <p>Another choice could be that interdependencies cannot be specified in the declarative syntax, but only through use of another function, for example setDependency().</p>

      <ac:macro ac:name="code"><ac:default-parameter>php</ac:default-parameter><ac:plain-text-body><![CDATA[
      $opts = new Zend_Console_Getopt('abp:');
      $opts->setDependency(array('a', 'b'), 'You must specify apple and banana options together.');

      <p>Then it throws an exception at the time it parses arguments. I.e. after any setArguments() calls have been made, and the app tries to query if a given option has been set. The message of this exception is the string that is the second argument to setDependency().</p>

      <p>How does this sound?</p>

  6. Dec 29, 2006

    <p>The setDependency sounds pretty much what I was thinking but what it if you only what add dependency one way?</p>

    <ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
    $opts = new Zend_Console_Getopt('abp:');

    $opts->setDependency(array('a', 'b'), 'You must specify apple with the banana option.');
    <p>So, that if you call "PROGRAM -a", it fails but "PROGRAM -b" works because option apple needs option banana but option banana doesn't need option apple.</p>

  7. Dec 29, 2006

    <p>I forgot to mention in my last comment that I ended up creating a wrapper class for all switchs/options. The option class containes methods to help add dependencies or groupedDependencies. This us a simple array that the parent "Getopt" class could then loop through and check to see if these switches/options were provided.</p>

    <p>What I mean by groupedDependencies is that I can have some common switches like: -email; and I want to make sure that if these switches are provided that another switch must be present to tell the program exactly what execution point to enter.</p>

    <p>For example; my program might have "<em>./program -report -email</em>" AND/OR ".<em>/program -alert -email</em>"</p>

    <p>Then I would create the -email option class and then add the -report and -alert option classes to it's groupedDependencies array. Then the Getopt class can now check if one or the other dependencies are present.</p>

    <p>I hope this makes sense; I've only just started creating my own Getopt in the last couple days based off what you have here and what my project needs.</p>

    1. Dec 29, 2006

      <p>I'm afraid it could become a slippery slope to try to support all sorts of application business rules. There is no end to the permutations. </p>

      <p>The presence of "-a" requires that either "-b" or "-c" are present.<br />
      But if <strong>both</strong> "-b" and "-c" are present, it's an error.<br />
      Unless "-a" is followed by a parameter of "both".<br />
      Or if "-f" is also present.<br />
      Or you're running as root.</p>

      <p>See what I mean? At what point does it become application-specific logic?</p>

      <p>I'm inclined to say go ahead and write your wrapper class. Getopt will tell you which options were given on the command-line by the user. It's up to you to write code to validate that specific combination of options.</p>

      <p>Your wrapper class is designed to be perfect for your application, but it may not be as useful to anyone else's applications. Or else it won't quite fit the needs of another application precisely. To solve everyone's needs, the class would become so huge that it would be too large to load.</p>

      <p>By the way, I made most of the methods and members protected instead of private, so you could extend Zend_Console_Getopt instead of writing a wrapper class. So if you need additional functions like dependency-handlers, write them in a subclass instead of a wrapper class. If the logic you need to implement is such that it can't be written in a subclass, but instead requires that it be done in a wrapper class, that's usually a good clue that it's part of your application logic, not part of the reusable component.</p>

  8. Jan 15, 2007

    <ac:macro ac:name="note"><ac:parameter ac:name="title">Zend Feedback</ac:parameter><ac:rich-text-body>
    <p>Retrieving CLI arguments is a task any developer coding cronjobs or CLI scripts in PHP is bound to face. GNU getopt is a powerful and widely accepted standard, and most PHP implementations do not implement the full featureset.</p>

    <p>After reviewing the incubator code and the proposal, we accept the proposed solution, with the following notes:</p>
    <li>Please review for CS issues:
    <li>put @todos listed in properties section but unattached to properties in the class docblock</li>
    <li>please add whitespace above docblocks (should be at least one line of whitespace above each for readability</li>
    <li>no closing PHP tag ('?>') at end of file, please</li>

    <p>Otherwise, the code and tests look very good! Thank you for this contribution.</p></ac:rich-text-body></ac:macro>

  9. Jan 31, 2007

    <p>I'm new to the Zend Framework, and have looked over this discussion and am a bit confused.  What I'm wondering is, if there is a way to have a cron job call a specific Module/Controller/Action from the command line.  I'm not understanding what these options are that the Zend_Console_Getopt Component deals with.  If there is not a specific way to access Module/Controller/Actions this way, I'm trying to use the classes created in the Zend Framework from an external .php file to do my cron jobs from.   And maybe that's what this component does, but i'm missing the picture.  Could someone post a good, easy to follow example that does just this?</p>

    1. Jan 31, 2007

      <p>I didn't intend the Zend_Console_Getopt class to have any implied integration with Zend_Controller. That was a feature request that some of us brought up.</p>

      <p>Getopt is a common function for many programming languages. It has nothing to do with MVC or web applications. Getopt is for command-line programs or scripts.</p>

      <p>Once there is an easy way to parse the command-line arguments in a PHP script invoked at the command-line, some of us thought that it would be neat if command-line usage could be an alternative way to invoke the same PHP code that they use as a web app. </p>

      <p>I had a discussion with Matthew Ratzloff about this some time ago when he was proposing a scheduler component (see <ac:link><ri:page ri:content-title="Zend_Scheduler - Matthew Ratzloff" /></ac:link>) that could invoke parts of a PHP web application according to a schedule. I suggested that a schedule task in cron or other existing schedule service could invoke a script with command-line arguments and the script would have code to recognize these arguments and map them into invocations of MVC controllers.</p>

      <p>But that mapping is not what Zend_Console_Getopt does. The Getopt class only parses the command-line arguments and provides an interface to querying which arguments were given by the user.</p>

      <p>It should be a different class that does that invocation of MVC controller methods. That class is not presented here. That hypothetical class may utilize Zend_Console_Getopt, but it deserves its own proposal.</p>

  10. Feb 14, 2007

    <p>I have committed documentation for Zend_Console_Getopt.</p>

    <p><zf-home>/incubator/documentation/manual/en/module_specs/Zend_Console_Getopt-Introduction.xml<br />
    <zf-home>/incubator/documentation/manual/en/module_specs/Zend_Console_Getopt-Rules.xml<br />
    <zf-home>/incubator/documentation/manual/en/module_specs/Zend_Console_Getopt-Fetching.xml<br />

    <p>If it looks good, I'll move the component to core.</p>