\documentclass{article}
\usepackage{ulem}
\usepackage{graphicx}
\usepackage{hyperref}
\pagestyle{headings}
\begin{document}
\part{Horde\_Argv}
\section{Extending Horde\_Argv}
Since the two major controlling factors in how \textit{Horde\_Argv} interprets command-line options are the action and type of each option, the most likely direction of extension is to add new actions and new types.

\subsection{Adding new types}
To add new types, you need to define your own subclass of the \texttt{Horde\_Argv\_Option class}. This class has a couple of properties that define \textit{Horde\_Argv}'s types: \texttt{\$TYPES} and \texttt{\$TYPE\_CHECKER}.

\texttt{\$TYPES} is a tuple of type names; in your subclass, simply define a new tuple \texttt{\$TYPES} that builds on the standard one.

\texttt{\$TYPE\_CHECKER} is a dictionary mapping type names to type-checking functions. A type-checking function has the following signature:

<pre><code class="language-php">
foo check\_foo(Horde\_Argv\_Option \$option, string \$opt, string \$value)
</code></pre>
You can name it whatever you like, and make it return any type you like (e.g. the hypothetical type foo). The value returned by a type-checking function will wind up in the \texttt{Horde\_Argv\_Values} instance returned by \texttt{Horde\_Argv\_Parser->parseArgs()}, or be passed to callbacks as the \texttt{\$value} parameter.

Your type-checking function should throw \texttt{Horde\_Argv\_OptionValueException} if it encounters any problems. \texttt{Horde\_Argv\_OptionValueException} takes a single string argument, which is passed as-is to \texttt{Horde\_Argv\_Parser}'s \texttt{parserError()} method, which in turn prepends the program name and the string \texttt{"error:"} and prints everything to stderr before terminating the process.

Here's a silly example that demonstrates adding an imaginary \texttt{MyComplex} option type to parse complex numbers on the command line.

You need to define your type-checker, since it's referred to in the \texttt{\$TYPE\_CHECKER} class attribute of your \texttt{Horde\_Argv\_Option} subclass:

<pre><code class="language-php">
class MyOption extends Horde\_Argv\_Option
\{
    public function \_\_construct()
    \{
        \$this->TYPES[] = 'complex';
        \$this->TYPE\_CHECKER['complex'] = 'checkComplex';
    \}

    public function checkComplex(\$option, \$opt, \$value)
    \{
        try \{
            return new MyComplex(value);
        \} catch (Exception \$e) \{
            throw new Horde\_Argv\_OptionValueException(
                sprintf('option \%s: invalid complex value: \%s', (opt, value))
            );
        \}
    \}
\}
</code></pre>
That's it! Now you can write a script that uses the new option type just like any other \textit{Horde\_Argv}-based script, except you have to instruct your \texttt{Horde\_Argv\_Parser} to use \texttt{MyOption} instead of \texttt{Horde\_Argv\_Option}:

<pre><code class="language-php">
\$parser = new Horde\_Argv\_Parser(array('optionClass' => 'MyOption'));
\$parser->addOption('-c', array('type' => 'complex'));
</code></pre>
Alternately, you can build your own option list and pass it to \texttt{Horde\_Argv\_Parser}; if you don't use \texttt{addOption()} in the above way, you don't need to tell \texttt{Horde\_Argv\_Parser} which option class to use:

<pre><code class="language-php">
\$option\_list = array(
    new MyOption(
        '-c',
        array('action' => 'store', 'type' => 'complex', 'dest' => 'c')
    )
);
parser = new Horde\_Argv\_Parser(array('optionList' => \$option\_list));
</code></pre>
\subsection{Adding new actions}
Adding new actions is a bit trickier, because you have to understand that \textit{Horde\_Argv} has a couple of classifications for actions:

\textbf{"store" actions}: actions that result in \textit{Horde\_Argv} storing a value to a property of the current \texttt{Horde\_Argv\_Values} instance; these options require a \texttt{dest} attribute to be supplied to the \texttt{Horde\_Argv\_Option} constructor<br />
\textbf{"typed" actions}: actions that take a value from the command line and expect it to be of a certain type; or rather, a string that can be converted to a certain type. These options require a type attribute to the \texttt{Horde\_Argv\_Option} constructor.

These are overlapping sets: some default "store" actions are \texttt{store}, \texttt{store\_const}, \texttt{append}, and \texttt{count}, while the default "typed" actions are \texttt{store}, \texttt{append}, and \texttt{callback}.

When you add an action, you need to decide if it's a "store" action, a "typed" action, neither, or both. Three class properties of \texttt{Horde\_Argv\_Option} (or your \texttt{Horde\_Argv\_Option} subclass) control this:

\textbf{\texttt{\$ACTIONS}}: all actions must be listed in \texttt{\$ACTIONS}<br />
\textbf{\texttt{\$STORE\_ACTIONS}}: "store" actions are additionally listed here<br />
\textbf{\texttt{\$TYPED\_ACTIONS}}: "typed" actions are additionally listed here

In order to actually implement your new action, you must override \texttt{Horde\_Argv\_Option}'s \texttt{takeAction()} method and add a case that recognizes your action.

For example, let's add an \texttt{extend} action. This is similar to the standard \texttt{append} action, but instead of taking a single value from the command-line and appending it to an existing list, extend will take multiple values in a single comma-delimited string, and extend an existing list with them. That is, if \texttt{"--names"} is an \texttt{extend} option of type \texttt{string}, the command line

<pre><code>
--names=foo,bar --names blah --names ding,dong
</code></pre>
would result in a list

<pre><code class="language-php">
array('foo', 'bar', 'blah', 'ding', 'dong')
</code></pre>
Again we define a subclass of \texttt{Horde\_Argv\_Option}:

<pre><code class="language-php">
class MyOption extends Horde\_Argv\_Option
\{
    public function \_\_construct()
    \{
        \$this->ACTIONS[] = 'extend';
        \$this->STORE\_ACTIONS[] = 'extend';
        \$this->TYPED\_ACTIONS[] = 'extend';
    \}

    public function takeAction(\$action, \$dest, \$opt, \$value, \$values, \$parser)
    \{
        if (\$action == 'extend') \{
            \$lvalue = explode(',', \$value);
            \$values->dest = array\_merge(\$values->ensureValue('dest', array()),
                                        \$lvalue);
        \} else \{
            parent::takeAction(\$action, \$dest, \$opt, \$value, \$values, \$parser);
        \}
    \}
\}
</code></pre>
Features of note:

\begin{itemize}
\item \texttt{extend} both expects a value on the command-line and stores that value somewhere, so it goes in both \texttt{\$STORE\_ACTIONS} and \texttt{\$TYPED\_ACTIONS}


\item \texttt{MyOption::takeAction()} implements just this one new action, and passes control back to \texttt{Horde\_Argv\_Option::takeAction()} for the standard \textit{Horde\_Argv} actions


\item \texttt{\$values} is an instance of the \texttt{Horde\_Argv\_Values} class, which provides the very useful \texttt{ensureValue()} method. \texttt{ensureValue()} is essentially a getter with a safety valve; it is called as


\end{itemize}
\begin{quote}
\texttt{\$values->ensureValue(\$attr, \$value)}<br />
If the \texttt{\$attr} property of \texttt{\$values} doesn't exist or is \texttt{null}, then \texttt{ensureValue()} first sets it to \texttt{\$value}, and then returns \texttt{\$value}. This is very handy for actions like \texttt{extend}, \texttt{append}, and \texttt{count}, all of which accumulate data in a variable and expect that variable to be of a certain type (an array for the first two, an integer for the latter). Using \texttt{ensureValue()} means that scripts using your action don't have to worry about setting a default value for the option destinations in question; they can just leave the default as \texttt{null} and \texttt{ensureValue()} will take care of getting it right when it's needed.


\end{quote}
\subsection{Other reasons to extend Horde\_Argv}
Adding new types and new actions are the big, obvious reasons why you might want to extend \textit{Horde\_Argv}. I can think of at least two other areas to play with.

First, the simple one: \texttt{Horde\_Argv\_Parser} tries to be helpful by calling \texttt{exit()} when appropriate, i.e. when there's an error on the command line or when the user requests help. In the former case, the traditional course of letting the script crash with a traceback is unacceptable; it will make users think there's a bug in your script when they make a command-line error. In the latter case, there's generally not much point in carrying on after printing a help message.

If this behaviour bothers you, it shouldn't be too hard to "fix" it. You'll have to

\begin{itemize}
\item subclass \texttt{Horde\_Argv\_Parser} and override \texttt{parserError()}


\item subclass \texttt{Horde\_Argv\_Option} and override \texttt{takeAction()} -- you'll need to provide your own handling of the \texttt{help} action that doesn't call \texttt{exit()}


\end{itemize}
The second, much more complex, possibility is to override the command-line syntax implemented by \textit{Horde\_Argv}. In this case, you'd leave the whole machinery of option actions and types alone, but rewrite the code that processes \texttt{argv}. You'll need to subclass \texttt{Horde\_Argv\_Parser} in any case; depending on how radical a rewrite you want, you'll probably need to override one or all of \texttt{parseArgs()}, \texttt{\_processLongOpt()}, and \texttt{\_processShortOpts()}.

Both of these are left as an exercise for the reader. I have not tried to implement either myself, since I'm quite happy with \textit{Horde\_Argv}'s default behaviour (naturally).

Happy hacking, and don't forget: Use the Source, Luke.

\end{document}
