thekid/rfc — Issue #102: RFC 0102: XP Class Runner
Author: thekid
Created: 2011-10-09 15:06:27+00:00
State: open
Labels: implemented, rfc

Scope of Change
====
A runner script (similar to the unittest runner) will be created to run
classes as scripts.

Rationale
====
Simplify command line utilities development.

Functionality
====

Overview
----
Instead of writing a complete script we now declare a class as follows:

```php
id= $id;
}

#[@inject(type= 'rdbms.DBConnection', name= 'news')]
function setConnection(&$conn) {
$this->conn= &$conn;
}

function run() {
$this->out->writeLine(xp::stringOf($conn->select(
'* from news where news_id= %d',
$this->id
)));
}
}
?>
```

We can run this class by using the cli-runner:

```
$ xpcli -c ~/.xp/news/ de.thekid.blog.DumpNews 1

```

Rules
----

```
* The class must extend the util.cmd.Command class

* Arguments and resources are injected by means of annotated methods

* We use an output stream to write data

* The class can be reused in different environments because it is not
dependant on anything console-specific.

```

The xpcli runner
----
Usage:

```
$ xpcli [runner-args]* [fully.qualified.ClassName] [class-args]*

```

The runner-args are any arguments beginning with a dash ("-"). The class
name argument is the first argument without a dash in front of it.

Runner-args:

```
* -c | --config
Path to configuration files, defaults to "etc/".

```

Class-args:

```
* -? | --help
Always supported. Shows a usage generated from the @arg-methods' apidoc
comments.

```

The runner goes through the following steps when invoked:

1) Extract runner arguments

2) Load command class

3) Check for -? or --help and show a usage

4) Configure the property manager

5) In case a database.ini exists in the property manager's configuration
file path, configure the connection manager with it

6) In case a log.ini exists in the property manager's configuration
file path, configure the logger with it

7) Perform dependency injection. In case an exception is thrown from any
of the methods, stop.

8) Pass arguments. In case an argument is missing or an exception is thrown
from any of the methods, stop.

9) Invoke the run() method

The util.cmd.Command class
----

```php

```
The out and err members are created by the runner and are
PrintOutputStream objects. For the console runner, they write data
to STDOUT and STDERR.

The @arg annotation
----
This annotation marks methods as parameter acceptors.

```
* Methods that wish to accept a command line argument must have exactly
one argument.

* This argument may have a default value.

* If the method does not have a default value, the parameter passing
stops hard at this point.

* Otherwise, the method is invoked without arguments, so that the
default value will be passed.

```

How to accept arguments:

1) Positional argument

```php

```
This will take the first command line argument

2) Named argument

```php

```
This will take the value passed as --verbosity= or -v

3) Auto-named argument

```php

```
This will take the value passed as --classname= or -c

The argument name is calculated by using a lowercase version of the
method name and stripping a leading "set".

4) Existance argument

```php
debug= TRUE;
}
?>
```
This method will be called only if --debug is supplied.

The argument name is calculated by using a lowercase version of the
method name and stripping a leading "set".

```
Note for cases 2), 3) and 4): The short argument's name is calculated by using
the first character of the long argument's name. It can be overwritten to
resolve ambiguous names by adding a short= '[CHARACTER]' to the @arg
annotation.

```

The @inject annotation
----
This annotation marks methods as resource injectors.

```
* Resources are one of the following: database connections, logger
categories, properties.

* Resources are configured by property objects:
- The property manager's lookup path is set to "etc/" per default
and may be set via xpcli -c
- The connection manager is configured by database.ini
- The logger is configured by log.ini

```

1) Injecting database connections

```php

```
This method will receive a DBConnection instance (via
ConnectionManager::getInstance()->getByHost('news', 0))

2) Injecting logger categories

```php

```
This method will receive a LogCategory instance (via
Logger::getInstance()->getCategory('default'))

3) Injecting properties

```php

```
This method will receive a LogCategory instance (via
PropertyManager::getInstance()->getProperties('app'))

Security considerations
====
n/a

Speed impact
====
n/a

Dependencies
====
- xp-framework/rfc #88
I/O Streams API

Related documents
====
- http://experiments.xp-framework.net/?people,friebe,cli
Experiment

- http://xp-framework.net/downloads/experiments/xpcli-5.0.0-alpha.tar.gz
Download (contains xpcli.php and supporting classes for PHP5)

Comments
====