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.
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"
app/vendors/shells/user.php
file, and change the name of the method main()
to add()
. 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'), }
_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; }
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:
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:
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 |
---|---|
|
A maximum number of records to process from the CSV file. If omitted, all records will be processed. |
|
The maximum length for the generated passwords. Defaults to |
|
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 |
---|---|
|
The name of the |
|
The full path to our application's root directory, which would contain the |
|
The name of the |
|
The full path to the |
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.
3.15.39.190