Parsing command line parameters

The recipe Building and running a shell showed us how to create a shell that adds records based on user-provided information. This recipe adds support to import accounts from a CSV file, while allowing the user to configure different settings through the use of command-line parameters.

Getting ready

To go through this recipe we need the user shell implemented. Follow the entire recipe Building and running a shell.

We will also need a sample CSV file from which to import records. Create a file named users.csv and place it in a directory of your choice (for example, in the application's app/tmp directory) with the following contents:

"john","John","Doe"
"jane","Jane","Doe"
"mark","Mark","Doe"
"mathew","Mathew","Doe"
"peter","Peter","Doe"
"roland","Roland","Doe"

How to do it...

  1. Edit your app/vendors/shells/user.php file, and change the name of the method main() to add().
  2. Add the following method right below the add() method:
    public function help() {
    $this->out('USAGE: $ cake '.$this->shell.' <import <path/to/file> [-limit N | -size N | -verbose] | add>'),
    $this->out('where:'),
    $this->out();
    $this->out('-limit N: import up to N records'),
    $this->out('-size N: size of generated password'),
    $this->out('-verbose: Verbose output'),
    }
    
  3. Now add the following method above the _randomPassword() method:
    protected function _parseCSV($path) {
    $file = fopen($path, 'r'),
    if (!is_resource($file)) {
    $this->error('Can't open '.$file);
    }
    $rows = array();
    while($row = fgetcsv($file)) {
    $rows[] = $row;
    }
    fclose($file);
    return $rows;
    }
    
  4. Finally, add the following below the help() method:
    public function import() {
    $this->_checkArgs(1);
    $defaults = array(
    'limit' => null,
    'size' => 10,
    'verbose' => false
    );
    $options = array_merge(
    $defaults,
    array_intersect_key($this->params, $defaults)
    );
    $path = $this->args[0];
    if (!is_file($path) || !is_readable($path)) {
    $this->error('File '.$path.' cannot be read'),
    }
    $users = array();
    foreach($this->_parseCSV($path) as $i => $row) {
    $users[$row[0]] = $this->_randomPassword($options['size']);
    if (!empty($options['limit']) && $i + 1 == $options['limit']) {
    break;
    }
    }
    if ($options['verbose']) {
    $this->out('Will create '.number_format(count($users)).' accounts'),
    }
    foreach($users as $userName => $password) {
    if ($options['verbose']) {
    $this->out('Creating user '.$userName.'... ', false);
    }
    $user = array('User' => array(
    'username' => $userName,
    'password' => Security::hash($password, null, true)
    ));
    $this->User->create();
    $saved = ($this->User->save($user) !== false);
    if (!$saved) {
    unset($users[$userName]);
    }
    if ($options['verbose']) {
    $this->out($saved ? 'SUCCESS' : 'FAIL'),
    }
    }
    $this->out('Created accounts:'),
    foreach($users as $userName => $password) {
    $this->out($userName.' : '.$password);
    }
    }
    

If we run the shell without arguments, CakePHP will say that there is no known command, and suggest that we get help by specifying help as an argument to our shell. Doing so will display our help message, as shown in the following screenshot:

How to do it...

If we run our shell with the add argument, we will see exactly the same functionality implemented in the recipe Building and running a shell.

Executing the shell with the import argument and the verbose parameter, and specifying the path to our CSV file with a command such as the following:

$ cake/console/cake user import app/tmp/users.csv -verbose

would import the users listed in the CSV file, generating an output similar to what is shown in the following screenshot:

How to do it...

How it works...

We started by changing the name of the entry method to add(). Doing so means we no longer have an entry method, so how does CakePHP find what to run when our shell is invoked? Through the use of commands.

If there is no entry method defined in a shell, CakePHP will assume that the first argument used when executing a shell is a command. A command is nothing more than a public method that does not start with an underscore sign. As such, a method named add() is executed when the shell is invoked with the add argument. If no argument is specified, CakePHP complains, as there is no command to run, and suggests the user use the help argument, which is nothing more than a way to call the help() method in our shell (as help is a regular command).

We use the help() method to show usage instructions for our shell, listing the available commands (add, and import), and the parameters for each of those commands. While the add command has no available parameters, we support the following parameters for our import command:

Setting

Purpose

limit

A maximum number of records to process from the CSV file. If omitted, all records will be processed.

size

The maximum length for the generated passwords. Defaults to 10.

verbose

If specified, the shell will output information as its creating the user records.

The _parseCSV() method is our helper method to parse a CSV file, returning an array of rows found in a file, where each row is itself an array of values. This method uses PHP's fgetcsv() function to parse a record from a file handle, obtained with the use of PHP's fopen() function, and closed with fclose() once the parsing is finished.

We continue by implementing the import() method, the body of our import command. This method uses the _checkArgs() method (available through the Shell class) to make sure that the command receives at least the specified number of arguments, in our case 1. If the method finds that the user did not specify the minimum number of arguments, it will throw an error message and abort the execution. This is a way for us to make sure that at least the path to the CSV file is provided.

If the number of arguments is correct we proceed to process the optional parameters. To do so, we use the params property. This property is available to all shells, and includes the following values even when no parameters are provided:

Setting

Purpose

app

The name of the app/ directory.

root

The full path to our application's root directory, which would contain the app/ and cake/ directories.

webroot

The name of the webroot/ directory, which is inside the app/ directory.

working

The full path to the app/ directory.

However, we are only interested in the parameters given by the user through the command line. Therefore, we define the set of valid parameters with their default values, and we merge the values for those parameters that are available in the params property. We store this merged values in an array named options.

Using the is_file() and is_readable() PHP functions, we make sure we were given a valid file. If not, we use the error() method to print out an error message and abort the application.

We then proceed to use _importCSV() to get a list of parsed rows, and for each of those rows we assign a random password, using the size option. We stop generating passwords once we reach the value of the limit option, if one is provided. By the end of this loop, we will have an array named users where its index is a username, and its value is the password for the given user.

For each of the values in the users array, we create the account record similar to the way we do it in the add command, while outputting the status of each creation if the verbose option is set. If we get an error while creating a specific record, we remove the problematic user from the users array.

Once the creation process is finalized, we output the list of successfully created usernames, together with their generated passwords.

See also

  • Parsing CSV files with a datasource in Chapter 5, Datasources
  • Creating reusable shell tasks
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.39.190