Federico Bond - tagged with php http://www.federicobond.com.ar/feed en-us http://blogs.law.harvard.edu/tech/rss Sweetcron federicobond+lifestream@gmail.com Managing Cron Jobs with PHP http://www.federicobond.com.ar/items/view/1488/managing-cron-jobs-with-php

The cronTab, or “Cron Table”, is a Linux system process / daemon which facilitates the scheduling of repetitive tasks thereby easing up our day to day routine.
In this tutorial, we’ll create a dynamic PHP class that, using a secure connection, provides us with a means to manipulate the cronTab!

Background – An Overview of the Crontab Let’s face it, having the ability to schedule tasks to run in the background is just great! From backing up an SQL database, fetching / sending emails to running clean up tasks, analyzing performance, or even grabbing RSS feeds, cron jobs are fantastic! Although the syntax of scheduling a new job may seem daunting at first glance, it’s actually relatively simple to understand once you break it down. A cron job will always have five columns each of which represent a chronological ‘operator’ followed by the full path and command to execute:

          • home/path/to/command/the_command.sh Each of the chronological columns has a specific relevance to the schedule of the task. They are as follows:

    Minutes represents the minutes of a given hour, 0-59 respectively.

    Hours represents the hours of a given day, 0-23 respectively.

    Days represents the days of a given month, 1-31 respectively.

    Months represents the months of a given year, 1-12 respectively.

    Day of the Week represents the day of the week, Sunday through Saturday, numerically, as 0-6 respectively.

    Minutes [0-59] | Hours [0-23] | | Days [1-31] | | | Months [1-12] | | | | Days of the Week [Numeric, 0-6] | | | | |

          • home/path/to/command/the_command.sh So, for example, if one wanted to schedule a task for 12am on the first day of every month it would look something like this:

    0 0 1 * * home/path/to/command/the_command.sh If we wanted to schedule a task to run every Saturday at 8:30am we’d write it as follows:

    30 8 * * 6 home/path/to/command/the_command.sh There are also a number of operators which can be used to customize the schedule even further:

    Commas is used to create a comma separated list of values for any of the cron columns.

    Dashes is used to specify a range of values.

    Asterisksis used to specify ‘all’ or ‘every’ value.

The cronTab, by default, will send an email notification whenever a scheduled task is executed. The cronTab, by default, will send an email notification whenever a scheduled task is executed. In many circumstances, though, this just isn’t needed. We can easily suppress this functionality, though, by redirecting the standard output of this command to the ‘black hole’ or /dev/null device. Essentially, this is a file that will discard everything written to it. Output redirection is done via the > operator. Let’s take a look at how we can redirect standard output to the black hole using our sample cron job which runs a scheduled task every Saturday at 8:30am:

30 8 * * 6 home/path/to/command/the_command.sh >/dev/null Additionally, if we’re redirecting the standard output to a the null device, we’ll probably want to redirect the standard errors as well. We can do this by simply redirecting standard errors to where the standard output is already redirected, the null device!

30 8 * * 6 home/path/to/command/the_command.sh >/dev/null 2>&1

Step 1 – The Blueprint In order to manage the cronTab with PHP, we’ll need the ability to execute commands, on the remote server, as the user whose cronTab we’re editing. Fortunately, PHP provides us with a simple way to do this via the SSH2 library. You may or may not have this library installed so if you don’t, you’ll want to get it installed: PHP libssh2 Installation / Configuration We’ll start our class off by declaring four properties, all of which will be private:

$connection represents our connection / resource. $path will represent the path for that file. $handle will represent the name of our temporary cron file. $cron_file represents the full path and name of the temporary cron file.

Our class must be able to connect and authenticate, as an appropriate user, in order to execute commands and have access the that user’s cronTab. Thus, we’ll establish an SSH2 connection and authenticate it within the constructor itself. With an authenticated connection in place, we’ll then need a method to handle the execution of the various commands we’ll be running. We’ll name this method exec(). Generally, we’ll call this method from within the other methods of our class when we need to execute a command on the remote server. Next, we’ll need the ability to write the cronTab to a file so that we have tangible access to it. We’ll also need a way to delete this file when we’re finished with it. Let’s define the method that handles outputting the cronTab to a file as write_to_file() and the method for removing this file as remove_file(). Of course, we’ll also need a way to create and remove cron jobs. So we’ll define an append_cronjob() and remove_cronjob() method, respectively. In the case that we’ve removed the only / last cronjob we’ll want the ability to remove the entire cronTab rather than trying to create an empty cronTab. We’ll use the method remove_crontab() to handle this. Lastly, we’ll create two helper methods for our class. The first of these methods, which will return a boolean value, will simply check for the existence of the temporary cron file. The latter will be used for displaying error messages should an error occur. We’ll name this methods crontab_file_exists() and error_message(), respectively.

<?php

Class Ssh2_crontab_manager {

private $connection; private $path; private $handle; private $cron_file;

function __construct() {...}

public function exec() {...}

public function write_to_file() {...}

public function remove_file() {...}

public function append_cronjob() {...}

public function remove_cronjob() {...}

public function remove_crontab() {...}

private function crontab_file_exists() {...}

private function error_message() {...}

}

Step 2 – The Constructor! The class constructor will primarily be responsible for establishing and authenticating the SSH2 connection. It will take four arguments, each of which will have a default value of NULL:

$host: represents the ip address of the remote server we want to connect to.

$port: is the port to be used for the SSH2 connection. $username: will represent the user’s log in name for the server. $password:represents the user’s password for the server.

function __construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) {...}

Within the constructor itself, the first property we’ll define is $this->path which will represent a ‘default directory’ for our temporary cron files. Ideally, we’d simply use PHP’s magic constant DIR to define this prop as the current working directory. There are, however, occasions where this constant may not be defined. As such, we’ll check to see if DIR is defined. If it’s not, we’ll have to get the current working directory manually. We’ll do so by using a combination of the strrpos() and substr() functions in conjunction with the FILE constant which represents the full path and name of the current file. We’ll use strrpos(), which returns the position of the last occurrence of a substring, to find the position of the last forward slash in the FILE string. This value, which we’ll store as the var $path_length, will give us the number of characters up to but not including the last forward slash. The substr() function will sort of ‘splice’ a string in that it returns a specific portion of a string based on the start position of the splice and the number of characters we want returned. We’ll pass three arguments to the substr() function

the string we’re working with the start location of the splice, in this case 0 the end location of the splice which is represented by the $path_length variable.

We’ll then concatenate a forward slash to the end of this string which will give us the current working directory.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; }

Now, we’ll define a default filename for the temporary cron file as $this->handle and then concatenate the path and handle props together as $this->cron_file. This prop will represent the full default path and filename for the temporary cron file.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}"; }

With these properties defined, we’ll now work on making a connection to the server and authenticating it. We’ll add some basic error handling to our class be wrapping the following code in a try / catch block. In this manner, we can catch errors and throw exceptions so as to provide the user with very specific feedback. Within the try block, we’ll first check to see that all of the arguments have been defined by using the is_null() function which will return true or false. If any of these arguments are NULL, we’ll throw a new exception with an appropriate message.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}";

try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) throw new Exception("Please specify the host, port, username and password!"); } catch {

} }

Next, we’ll define $this->connection by passing the $host and $port arguments to the ssh2_connect() function which will establish a remote connection and return that resource or FALSE if the connection fails. Since we’re using our own error handling, we’ll also use the error control operator @ which will suppress any error messages for this function. If the connection is not successful, we’ll throw a new exception with an appropriate message accordingly.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}";

try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) throw new Exception("Please specify the host, port, username and password!");

$this->connection = @ssh2_connect($host, $port); if ( ! $this->connection) throw new Exception("The SSH2 connection could not be established."); } catch {

} }

We’ll now attempt to authenticate / log in using the ssh2_auth_password() function passing in the resource returned from our connection as well as the username and the password of the user we’re logging in. ssh2_auth_password() will return a boolean that represents the status of the authentication using which we can throw a new exception if the authentication fails.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}";

try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) throw new Exception("Please specify the host, port, username and password!");

$this->connection = @ssh2_connect($host, $port); if ( ! $this->connection) throw new Exception("The SSH2 connection could not be established.");

$authentication = @ssh2_auth_password($this->connection, $username, $password); if ( ! $authentication) throw new Exception("Could not authenticate '{$username}' using password: '{$password}'."); } catch {

} }

PHP will try to find a matching catch block, for each exception which was thrown, and then create / pass an exception object, which contains a number of useful properties, to this block.
So, in the catch block, we’ll use the exception object’s getMessage() method to access and display the message defined in the exception. You’ll notice that we’ll be displaying the message by calling the error_message() method of our class. Although this method is not defined yet, it will simply display error messages in a tidy manner.

function construct($host=NULL, $port=NULL, $username=NULL, $password=NULL) { $path_length = strrpos(__FILE, "/"); $this->path = substr(FILE, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}";

try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) throw new Exception("Please specify the host, port, username and password!");

$this->connection = @ssh2_connect($host, $port); if ( ! $this->connection) throw new Exception("The SSH2 connection could not be established.");

$authentication = @ssh2_auth_password($this->connection, $username, $password); if ( ! $authentication) throw new Exception("Could not authenticate '{$username}' using password: '{$password}'."); } catch (Exception $e) { $this->error_message($e->getMessage()); } }

Step 3 – Executing Multiple Commands This method will be responsible for executing commands on the remote server. It’s comparable to manually entering commands into a shell like, say, PuTTY. To allow for greater flexibility, we’ll make this method public so that users can actually execute any other commands they may need to run. We’ll also allow for any number of arguments so long as at least one is specified. These arguments will represent the commands we want to run using the ssh2_exec() function. So, the first thing we’ll do in this method is define a variable representing a count of the total number of arguments passed. We’ll use PHP’s func_num_args() function to get this count.

public function exec() { $argument_count = func_num_args(); } Now, within a try block, we’ll check whether or not any arguments we’re passed to this method. If the argument count is 0, we’ll throw a new exception with an appropriate message.

public function exec() { $argument_count = func_num_args();

try { if ( ! $argument_count) throw new Exception("There is nothing to execute, no arguments specified."); } catch {

} } Next, using the func_get_args() function we’ll create an array of all the arguments which were passed to this method. Using a ternary operator, we’ll then define a new variable, $command_string, as a single line string representation of the actual Linux commands we’ll be executing. If we do have multiple commands to execute, we’ll use PHP’s implode() function to parse the arguments array into a string. We’ll pass two arguments to implode(), the glue, or separator, which is basically just a string that will be inserted between the array elements, and the array itself. We’ll separate each element with the Linux operator, && which will allow us to execute multiple commands, sequentially, on one line! Conversely, if there is only one command, we’ll define the command string as $arguments[0] which represents the first and only argument / command.

public function exec() { $argument_count = func_num_args();

try { if ( ! $argument_count) throw new Exception("There is nothing to execute, no arguments specified.");

$arguments = func_get_args();

$command_string = ($argument_count > 1) ? implode(" && ", $arguments) : $arguments[0];

} catch {

} } With our commands ready and parsed as a string, we can now try to execute them using the ssh2_exec() function. We’ll pass the connection link id, $this->connection, as well as our $command_string to this function which will return a stream on success or false on failure. Streams are defined as a resource object which exhibits streamable behavior… which can be read from or written to in a linear fashion. We’ll use the error control operator @ here, again, to suppress any error messages as we’ll be throwing our own exception, with an appropriate message, should an error occur.

public function exec() { $argument_count = func_num_args();

try { if ( ! $argument_count) throw new Exception("There is nothing to execute, no arguments specified.");

$arguments = func_get_args();

$command_string = ($argument_count > 1) ? implode(" && ", $arguments) : $arguments[0];

$stream = @ssh2_exec($this->connection, $command_string); if ( ! $stream) throw new Exception("Unable to execute the specified commands: {$command_string}");

} catch {

} } That’s it for the try block! Within the catch block, we’ll simply call the error_message() method in order to display any error messages to our user. With the try and catch blocks now complete, we’ll return $this at the end of the exec() method which will make this method chainable!

public function exec() { $argument_count = func_num_args();

try { if ( ! $argument_count) throw new Exception("There is nothing to execute, no arguments specified.");

$arguments = func_get_args();

$command_string = ($argument_count > 1) ? implode(" && ", $arguments) : $arguments[0];

$stream = @ssh2_exec($this->connection, $command_string); if ( ! $stream) throw new Exception("Unable to execute the specified commands: {$command_string}");

} catch { $this->error_message($e->getMessage()); }

return $this; }

Step 4 – Writing the CronTab to a File The next method, write_to_file(), will be responsible for writing the existing cronTab to a temporary file or creating a blank temp. file should no cron jobs exist. It will take two arguments

the path for the temporary file we’ll be creating the name we should use when creating it.

Continuing with the logic of our constructor and exec methods, we’ll set default values for these arguments as NULL.

public function write_to_file($path=NULL, $handle=NULL) {

} The first thing we’ll do here is check to see if the cron file already exists by using the boolean crontab_file_exists() method, which we’ll create shortly. If the file does exist, there’s no need to proceed. If it doesn’t, we’ll use a ternary operator to check the $path and $handle props to determine whether or not they’re NULL. If either of them are NULL, we’ll use the predefined fallbacks from our constructor to define them. Then, we’ll concatenate these properties together into a new property which will represent the full path and file name for the temporary cron file.

public function write_to_file($path=NULL, $handle=NULL) { if ( ! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path;

$this->cron_file = "{$this->path}{$this->handle}"; } } Next, we’ll use the Linux command crontab, with the -l argument set, to display the users cronTab as standard output. Then, using Linux’s > operator, we’ll redirect standard output, or STDOUT, to our temporary cron file instead by concatenating $this->cron_file into the command string! Redirecting output to a file, using the > operator, will always create that file if it doesn’t exist.

public function write_to_file($path=NULL, $handle=NULL) { if ( ! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path;

$this->cron_file = "{$this->path}{$this->handle}";

$init_cron = "crontab -l > {$this->cron_file}"; } } This works very well but only if there are already jobs scheduled within the cronTab. If there are no cron jobs, however, this file will never be created! Using the && operator though, we can append additional commands / expressions to this string. So, let’s append a conditional expression to check that the cron file exists. If the file doesn’t exist, this expression will evaluate to false. As such, we can then use the || or “or” operator after this conditional to create a new blank file if needed! Commands placed after “or” will execute if the condition / conditions evaluate to false. Now, by using the > operator again, this time without preceding it by a specific command, we can create a new blank file! So, essentially, this string of commands will output the cronTab to a file, then check if that file exists, which would indicate that there are entries in the cronTab and then create a new blank file if it doesn’t already exist!

public function write_to_file($path=NULL, $handle=NULL) { if ( ! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path;

$this->cron_file = "{$this->path}{$this->handle}";

$init_cron = "crontab -l > {$this->cron_file} && [ -f {$this->cron_file} ] || > {$this->cron_file}"; } } Lastly, we’ll call the exec() method and pass the command string to it as the only argument. Then, in order to make this method chainable as well, we’ll return $this.

public function write_to_file($path=NULL, $handle=NULL) { if ( ! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path;

$this->cron_file = "{$this->path}{$this->handle}";

$init_cron = "crontab -l > {$this->cron_file} && [ -f {$this->cron_file} ] || > {$this->cron_file}";

$this->exec($init_cron); }

return $this; }

Step 5 – Removing the Temporary Cron File This method, remove_file() is as easy as easy could be! We’ll use our helper method, crontab_file_exists(), to check for the existence of the temporary cron file and then execute the Linux command rm to delete it if it does. As usual, we’ll also return $this in order to maintain chainability.

public function remove_file() { if ($this->crontab_file_exists()) $this->exec("rm {$this->cron_file}");

return $this; }

Step 6 – Creating New Cron Jobs This method will create new cron jobs by way of adding new jobs / lines to the temporary cron file and then executing the crontab command on that file which will install all of those jobs as a new cronTab. As such, append_cronjob() will take one argument, $cron_jobs, which will either be a string or an array of strings representing the cron jobs to append. We’ll start this method off by determining if the $cron_jobs argument is NULL. If it is, we’ll call the error_message() method in order to halt any further execution and display an error message to the user.

public function append_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to append! Please specify a cron job or an array of cron jobs.");

} Essentially, we can just echo our task into the cron file by redirecting standard output into the file again. Next, we’ll define a new variable, $append_cronfile, as a string, with the text “echo” followed by a space and one single quote at the end. We’ll be populating this string with the various cron jobs we’re adding as well as the closing quote, momentarily. We’ll be building this string using the string concatenation operator .=. Using a ternary operator, we’ll determine if $cron_jobs is an array or not. If it is, we’ll implode that array with new lines, \n, so that each cron job is written on it’s own line within the cron file. If the $cron_jobs argument is not an array, we’ll simply concatenate that job onto the $append_cron string without any special processing. In this manner, we’ll have a properly formatted string regardless of whether or not we’re working with an array. Essentially, we can just echo our task into the cron file by redirecting standard output into the file again. So, using the string concatenation operator, we’ll append the closing single quote to the command string as well as the Linux operator >> followed by the full path and file name for the cron file. The >> operator, unlike the > operator which always overwrites a file, appends output to the end of a file. So by using this operator, we won’t overwrite any existing cron jobs.

public function append_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to append! Please specify a cron job or an array of cron jobs.");

$append_cronfile = "echo '";

$append_cronfile .= (is_array($cron_jobs)) ? implode("\n", $cron_jobs) : $cron_jobs;

$append_cronfile .= "' >> {$this->cron_file}";

} We’ll now define a variable, as a string, with the command we’re going to use to install the new cron file which we’re about to create! This is as simple as calling the crontab command followed by the path and filename of the the cron file. Before executing these commands via the exec() method, though, we’ll first call the write_to_file() method in order to create the temporary cron file. Then, within a chain, we’ll execute these commands and call the remove_file() method to delete the temporary file. Lastly, we’ll return $this so that the append_cronjob() method is chainable.

public function append_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to append! Please specify a cron job or an array of cron jobs.");

$append_cronfile = "echo '";

$append_cronfile .= (is_array($cron_jobs)) ? implode("\n", $cron_jobs) : $cron_jobs;

$append_cronfile .= "' >> {$this->cron_file}";

$install_cron = "crontab {$this->cron_file}";

$this->write_to_file()->exec($append_cronfile, $install_cron)->remove_file();

return $this; }

Step 7 – Removing Existing Cron Jobs Now that we can create new cron jobs, it’s only logical that we have the ability to remove them as well! The remove_cronjob() method will take one argument which will be a (simple) regular expression. This regEx will be used to find matching jobs within the cronTab and remove them accordingly. As with the append_cronjob() method, the first thing we’ll do is check to see if the $cron_jobs argument is NULL and halt execution if it is. If not, we’ll call the create_file() method in order write the cron tab to a file.

public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs.");

$this->write_to_file();

} With the cron file created, we’ll now read it into an array using PHP’s file() function. This function will parse a given file into array with each line as an array element. We’ll pass our cron file to this function as the first argument and then set a special flag, FILE_IGNORE_NEW_LINES, which will force file() to ignore all new lines. Thus, we’ll have an array with only the cron jobs themselves! Should there be no cron jobs scheduled, this array will be empty. Subsequently, there will be no reason to continue. Thus, we’ll check to see if the $cron_array is empty and halt execution if it is. If the cronTab is not empty, we’ll then count the elements in the $cron_array using PHP’s count() function. We’ll store this value as $original_count. We’ll use it, shortly, to determine if we’ve removed any cron jobs from the $cron_array.

public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs.");

$this->write_to_file();

$cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES);

if (is_empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty.");

$original_count = count($cron_array);

} Now, we’ll determine if the $cron_jobs argument is an array or not. If it is an array, we’ll iterate through it with a foreach loop. Within that loop we’ll only execute one function, preg_grep(). This nifty function, not unlike preg_match(), will return an array of all the array elements that match the regular expression specified. In this case, however, we want the array elements that don’t match. In other words, we need an array of all the cron jobs that we’re going to keep so that we can initialize the cronTab with just these jobs. As such, we’ll set a special flag here, PREG_GREP_INVERT, which will cause preg_grep() to return an array of all the elements which don’t match the regular expression. Thus, we’ll have an array of all the cron jobs we want to keep!

public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs.");

$this->write_to_file();

$cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES);

if (is_empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty.");

$original_count = count($cron_array);

if (is_array($cron_jobs)) { foreach ($cron_jobs as $cron_regex) $cron_array = preg_grep($cron_regex, $cron_array, PREG_GREP_INVERT); } else {

} } If the $cron_jobs argument is not an array, we’ll proceed in much the same manner but without any iteration. Again, we’ll redefine $cron_array as the resulting array from preg_grep() function with the PREG_GREP_INVERT flag set.

public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs.");

$this->write_to_file();

$cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES);

if (is_empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty.");

$original_count = count($cron_array);

if (is_array($cron_jobs)) { foreach ($cron_jobs as $cron_regex) $cron_array = preg_grep($cron_regex, $cron_array, PREG_GREP_INVERT); } else { $cron_array = preg_grep($cron_jobs, $cron_array, PREG_GREP_INVERT); } } With our $cron_array set, now, we’ll compare the current length of this array with it’s original length which we cached in the variable $original_count. If the lengths are identical, we’ll simply return the remove_file() method to delete the temporary cron file. If they don’t match, we’ll remove the existing cronTab and then install the new one! The remove_file(), remove_crontab() and append_cronjob() methods all return $this so by returning these methods we’re still preserving this methods chainability.

public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs.");

$this->write_to_file();

$cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES);

if (is_empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty.");

$original_count = count($cron_array);

if (is_array($cron_jobs)) { foreach ($cron_jobs as $cron_regex) $cron_array = preg_grep($cron_regex, $cron_array, PREG_GREP_INVERT); } else { $cron_array = preg_grep($cron_jobs, $cron_array, PREG_GREP_INVERT); }

return ($original_count === count($cron_array)) ? $this->remove_file() : $this->remove_crontab()->append_cronjob($cron_array); }

Step 8 – Removing the Entire Crontab Removing the entire cronTab is relatively simple to do. Essentially, we’ll just execute the crontab command with the -r flag set which removes the entire cronTab for a given user. Since the crontab has been removed we might as well remove the temporary cron file as well, should it exist. Then we’ll return $this in order to preserve chainability.

public function remove_crontab() { $this->exec("crontab -r")->remove_file();

return $this; }

Step 9 – A Few Helpful Methods With the brunt of our cron management class written, we’ll now take a look at the two small but useful methods we’ve used throughout our class, crontab_file_exists() and error_message().

$this-&gt;crontab_file_exists()

This method will simply return the result of PHP’s file_exists() method, true or false, depending on whether or not the temporary cron file exists.

private function crontab_file_exists() { return file_exists($this->cron_file); }

$this-&gt;error_message($error)

This method will take one argument, a string, representing the error message we want to display. We’ll then call PHP’s die() method in order to halt execution and display this message. The string it self, will be concatenated into a <pre> element with some simple style applied to it.

private function error_message($error) { die("<pre style='color:#EE2711'>ERROR: {$error}</pre>"); }

Step 10 – Putting it all together! Now that we’ve completed our cron management class, let’s take a look at a few examples of how to use it!

Instantiating the class and establishing an authenticated connection:

First, let’s create a new instance of our class. Remember, we’ll need to pass the ip address, port, username and password to the class constructor.

$crontab = new Ssh2_crontab_manager('11.11.111.111', '22', 'my_username', 'my_password');

Appending a single cron job:

With an authenticated connection in place, let’s have a look at how we can create a new, single, cron job.

$crontab = new Ssh2_crontab_manager('11.11.111.111', '22', 'my_username', 'my_password'); $crontab->append_cronjob('30 8 * * 6 home/path/to/command/the_command.sh >/dev/null 2>&1');

Appending an array of cron jobs:

Appending multiple cron jobs is just as easy as appending a single cron job. We’ll simply passing an array to the append_cronjob() method.

$crontab = new Ssh2_crontab_manager('11.11.111.111', '22', 'my_username', 'my_password');

$new_cronjobs = array( '0 0 1 * * home/path/to/command/the_command.sh', '30 8 * * 6 home/path/to/command/the_command.sh >/dev/null 2>&1' );

$crontab->append_cronjob($new_cronjobs);

Removing a single cron job:

In like manner to how we created a single cron job, we’ll now remove one. This time, however, we’ll use a regular expression to find the appropriate task. This regEx can be as simple or as complex as you need it to be. In fact, there are a number of ways to regex for the task you’re looking for. For example, if the task you need to remove is unique in that the command being run is not used anywhere else in the cronTab, you could simple specify the command name as you’re regEx. Furthermore, if you wanted to remove all the tasks for a certain month, you could simply write a regular expression to find a match for all the jobs of a given month!

$crontab = new Ssh2_crontab_manager('11.11.111.111', '22', 'my_username', 'my_password');

$cron_regex = '/home\/path\/to\/command\/the_command.sh\/';

$crontab->remove_cronjob($cron_regex);

Removing an array of cron jobs:

Removing multiple cronjobs is handled in the same manner as removing a single cronJob with a single exception, we'll pass an array of cron job regular expressions to the remove_cronjob() method.

$crontab = new Ssh2_crontab_manager('11.11.111.111', '22', 'my_username', 'my_password');

$cron_regex = array( '0 0 1 * *', '/home\/path\/to\/command\/the_command.sh\/' );

$crontab->remove_cronjob($cron_regex);

Conclusion That's all folks! I hope you've enjoyed reading this article as much as I've enjoyed writing it and that you've gained new insights into the cronTab and managing it with PHP. Thank you so much for reading!

]]>
Thu, 07 Apr 2011 07:30:00 -0700 http://www.federicobond.com.ar/items/view/1488/managing-cron-jobs-with-php
Techniques for Mastering cURL http://www.federicobond.com.ar/items/view/476/techniques-for-mastering-curl

cURL is a tool for transferring files and data with URL syntax, supporting many protocols including HTTP, FTP, TELNET and more. Initially, cURL was designed to be a command line tool. Lucky for us, the cURL library is also supported by PHP. In this article, we will look at some of the advanced features of cURL, and how we can use them in our PHP scripts.Why cURL?It’s true that there are other ways of fetching the contents of a web page. Many times, mostly due to laziness, I have just used simple PHP functions instead of cURL: $content = file_get_contents("http://www.nettuts.com");

// or

$lines = file("http://www.nettuts.com");

// or

readfile("http://www.nettuts.com"); However they have virtually no flexibility and lack sufficient error handling. Also, there are certain tasks that you simply can not do, like dealing with cookies, authentication, form posts, file uploads etc.cURL is a powerful library that supports many different protocols, options, and provides detailed information about the URL requests.Basic StructureBefore we move on to more complicated examples, let’s review the basic structure of a cURL request in PHP. There are four main steps:InitializeSet OptionsExecute and Fetch Result Free up the cURL handle // 1. initialize $ch = curl_init();

// 2. set the options, including the url curl_setopt($ch, CURLOPT_URL, "http://www.nettuts.com"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HEADER, 0);

// 3. execute and fetch the resulting HTML output $output = curl_exec($ch);

// 4. free up the curl handle curl_close($ch); Step #2 (i.e. curl_setopt() calls) is going to be a big part of this article, because that is where all the magic happens. There is a long list of cURL options that can be set, which can configure the URL request in detail. It might be difficult to go through the whole list and digest it all at once. So today, we are just going to use some of the more common and useful options in various code examples.Checking for ErrorsOptionally, you can also add error checking: // ...

$output = curl_exec($ch);

if ($output === FALSE) {

echo "cURL Error: " . curl_error($ch);

}

// ... Please note that we need to use “=== FALSE” for comparison instead of “== FALSE”. Because we need to distinguish between empty output vs. the boolean value FALSE, which indicates an error.Getting InformationAnother optional step is to get information about the cURL request, after it has been executed. // ...

curl_exec($ch);

$info = curl_getinfo($ch);

echo 'Took ' . $info['total_time'] . ' seconds for url ' . $info['url'];

// ... Following information is included in the returned array:“url”“content_type”“http_code”“header_size”“request_size”“filetime”“ssl_verify_result”“redirect_count”“total_time”“namelookup_time”“connect_time”“pretransfer_time”“size_upload”“size_download”“speed_download”“speed_upload”“download_content_length”“upload_content_length”“starttransfer_time”“redirect_time”Detect Redirection Based on BrowserIn this first example, we will write a script that can detect URL redirections based on different browser settings. For example, some websites redirect cellphone browsers, or even surfers from different countries.We are going to be using the CURLOPT_HTTPHEADER option to set our outgoing HTTP Headers including the user agent string and the accepted languages. Finally we will check to see if these websites are trying to redirect us to different URLs. // test URLs $urls = array( "http://www.cnn.com", "http://www.mozilla.com", "http://www.facebook.com" ); // test browsers $browsers = array(

"standard" => array ( "user_agent" => "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", "language" => "en-us,en;q=0.5" ),

"iphone" => array ( "user_agent" => "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A537a Safari/419.3", "language" => "en" ),

"french" => array ( "user_agent" => "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; GTB6; .NET CLR 2.0.50727)", "language" => "fr,fr-FR;q=0.5" )

);

foreach ($urls as $url) {

echo "URL: $url\n";

foreach ($browsers as $test_name => $browser) {

$ch = curl_init();

// set url curl_setopt($ch, CURLOPT_URL, $url);

// set browser specific headers curl_setopt($ch, CURLOPT_HTTPHEADER, array( "User-Agent: {$browser['user_agent']}", "Accept-Language: {$browser['language']}" ));

// we don't want the page contents curl_setopt($ch, CURLOPT_NOBODY, 1);

// we need the HTTP Header returned curl_setopt($ch, CURLOPT_HEADER, 1);

// return the results instead of outputting it curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

$output = curl_exec($ch);

curl_close($ch);

// was there a redirection HTTP header? if (preg_match("!Location: (.*)!", $output, $matches)) {

echo "$test_name: redirects to $matches[1]\n";

} else {

echo "$test_name: no redirection\n";

}

} echo "\n\n"; } First we have a set of URLs to test, followed by a set of browser settings to test each of these URLs against. Then we loop through these test cases and make a cURL request for each.Because of the way setup the cURL options, the returned output will only contain the HTTP headers (saved in $output). With a simple regex, we can see if there was a “Location:” header included.When you run this script, you should get an output like this:POSTing to a URLOn a GET request, data can be sent to a URL via the “query string”. For example, when you do a search on Google, the search term is located in the query string part of the URL:http://www.google.com/search?q=nettutsYou may not need cURL to simulate this in a web script. You can just be lazy and hit that url with “file_get_contents()” to receive the results.But some HTML forms are set to the POST method. When these forms are submitted through the browser, the data is sent via the HTTP Request body, rather than the query string. For example, if you do a search on the CodeIgniter forums, you will be POSTing your search query to:http://codeigniter.com/forums/do_search/We can write a PHP script to simulate this kind of URL request. First let’s create a simple file for accepting and displaying the POST data. Let’s call it post_output.php: print_r($_POST); Next we create a PHP script to perform a cURL request: $url = "http://localhost/post_output.php";

$post_data = array ( "foo" => "bar", "query" => "Nettuts", "action" => "Submit" );

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // we are doing a POST request curl_setopt($ch, CURLOPT_POST, 1); // adding the post variables to the request curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$output = curl_exec($ch);

curl_close($ch);

echo $output; When you run this script, you should get an output like this:It sent a POST to the post_output.php script, which dumped the $_POST variable, and we captured that output via cURL.File UploadUploading files works very similarly to the previous POST example, since all file upload forms have the POST method.First let’s create a file for receiving the request and call it upload_output.php: print_r($_FILES); And here is the actual script performing the file upload: $url = "http://localhost/upload_output.php";

$post_data = array ( "foo" => "bar", // file to be uploaded "upload" => "@C:/wamp/www/test.zip" );

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_POST, 1);

curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);

$output = curl_exec($ch);

curl_close($ch);

echo $output; When you want to upload a file, all you have to do is pass its file path just like a post variable, and put the @ symbol in front of it. Now when you run this script you should get an output like this:Multi cURLOne of the more advanced features of cURL is the ability to create a “multi” cURL handle. This allows you to open connections to multiple URLs simultaneously and asynchronously.On a regular cURL request, the script execution stops and waits for the URL request to finish before it can continue. If you intend to hit multiple URLs, this can take a long time, as you can only request one URL at a time. We can overcome this limitation by using the multi handle.Let’s look at this sample code from php.net: // create both cURL resources $ch1 = curl_init(); $ch2 = curl_init();

// set URL and other appropriate options curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/"); curl_setopt($ch1, CURLOPT_HEADER, 0); curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/"); curl_setopt($ch2, CURLOPT_HEADER, 0);

//create the multiple cURL handle $mh = curl_multi_init();

//add the two handles curl_multi_add_handle($mh,$ch1); curl_multi_add_handle($mh,$ch2);

$active = null; //execute the handles do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);

while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } }

//close the handles curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_close($mh); The idea is that you can open multiple cURL handles and assign them to a single multi handle. Then you can wait for them to finish executing while in a loop.There are two main loops in this example. The first do-while loop repeatedly calls curl_multi_exec(). This function is non-blocking. It executes as little as possible and returns a status value. As long as the returned value is the constant ‘CURLM_CALL_MULTI_PERFORM’, it means that there is still more immediate work to do (for example, sending http headers to the URLs.) That’s why we keep calling it until the return value is something else.In the following while loop, we continue as long as the $active variable is ‘true’. This was passed as the second argument to the curl_multi_exec() call. It is set to ‘true’ as long as there are active connections withing the multi handle. Next thing we do is to call curl_multi_select(). This function is ‘blocking’ until there is any connection activity, such as receiving a response. When that happens, we go into yet another do-while loop to continue executing.Let’s see if we can create a working example ourselves, that has a practical purpose.Wordpress Link CheckerImagine a blog with many posts containing links to external websites. Some of these links might end up dead after a while for various reasons. Maybe the page is longer there, or the entire website is gone.We are going to be building a script that analyzes all the links and finds non-loading websites and 404 pages and returns a report to us.Note that this is not going to be an actual Wordpress plug-in. It is only a standalone utility script, and it is just for demonstration purposes.So let’s get started. First we need to fetch the links from the database: // CONFIG $db_host = 'localhost'; $db_user = 'root'; $db_pass = ''; $db_name = 'wordpress'; $excluded_domains = array( 'localhost', 'www.mydomain.com'); $max_connections = 10; // initialize some variables $url_list = array(); $working_urls = array(); $dead_urls = array(); $not_found_urls = array(); $active = null;

// connect to MySQL if (!mysql_connect($db_host, $db_user, $db_pass)) { die('Could not connect: ' . mysql_error()); } if (!mysql_select_db($db_name)) { die('Could not select db: ' . mysql_error()); }

// get all published posts that have links $q = "SELECT post_content FROM wp_posts WHERE post_content LIKE '%href=%' AND post_status = 'publish' AND post_type = 'post'"; $r = mysql_query($q) or die(mysql_error()); while ($d = mysql_fetch_assoc($r)) {

// get all links via regex if (preg_match_all("!href=\"(.*?)\"!", $d['post_content'], $matches)) {

foreach ($matches[1] as $url) {

// exclude some domains $tmp = parse_url($url); if (in_array($tmp['host'], $excluded_domains)) { continue; }

// store the url $url_list []= $url; } } }

// remove duplicates $url_list = array_values(array_unique($url_list));

if (!$url_list) { die('No URL to check'); } First we have some database configuration, followed by an array of domain names we will ignore ($excluded_domains). Also we set a number for maximum simultaneous connections we will be using later ($max_connections). Then we connect to the database, fetch posts that contain links, and collect them into an array ($url_list).Following code might be a little complex, so I will try to explain it in small steps. // 1. multi handle $mh = curl_multi_init();

// 2. add multiple URLs to the multi handle for ($i = 0; $i < $max_connections; $i++) { add_url_to_multi_handle($mh, $url_list); }

// 3. initial execution do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);

// 4. main loop while ($active && $mrc == CURLM_OK) {

// 5. there is activity if (curl_multi_select($mh) != -1) {

// 6. do work do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM);

// 7. is there info? if ($mhinfo = curl_multi_info_read($mh)) { // this means one of the requests were finished

// 8. get the info on the curl handle $chinfo = curl_getinfo($mhinfo['handle']);

// 9. dead link? if (!$chinfo['http_code']) { $dead_urls []= $chinfo['url'];

// 10. 404? } else if ($chinfo['http_code'] == 404) { $not_found_urls []= $chinfo['url'];

// 11. working } else { $working_urls []= $chinfo['url']; }

// 12. remove the handle curl_multi_remove_handle($mh, $mhinfo['handle']); curl_close($mhinfo['handle']);

// 13. add a new url and do work if (add_url_to_multi_handle($mh, $url_list)) {

do {
 $mrc = curl_multi_exec&#40;$mh, $active&#41;;
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

} } } }

// 14. finished curl_multi_close($mh);

echo "==Dead URLs==\n"; echo implode("\n",$dead_urls) . "\n\n";

echo "==404 URLs==\n"; echo implode("\n",$not_found_urls) . "\n\n";

echo "==Working URLs==\n"; echo implode("\n",$working_urls);

// 15. adds a url to the multi handle function add_url_to_multi_handle($mh, $url_list) { static $index = 0;

// if we have another url to get if ($url_list[$index]) {

// new curl handle $ch = curl_init();

// set the url curl_setopt($ch, CURLOPT_URL, $url_list[$index]); // to prevent the response from being outputted curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // follow redirections curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // do not need the body. this saves bandwidth and time curl_setopt($ch, CURLOPT_NOBODY, 1);

// add it to the multi handle curl_multi_add_handle($mh, $ch);

// increment so next url is used next time $index++;

return true; } else {

// we are done adding new URLs return false; } } And here is the explanation for the code above. Numbers in the list correspond to the numbers in the code comments.Created a multi handle.We will be creating the add_url_to_multi_handle() function later on. Every time it is called, it will add a url to the multi handle. Initially, we add 10 (based on $max_connections) URLs to the multi handle.We must run curl_multi_exec() for the initial work. As long as it returns CURLM_CALL_MULTI_PERFORM, there is work to do. This is mainly for creating the connections. It does not wait for the full URL response.This main loop runs as long as there is some activity in the multi handle.curl_multi_select() waits the script until an activity to happens with any of the URL quests.Again we must let cURL do some work, mainly for fetching response data.We check for info. There is an array returned if a URL request was finished.There is a cURL handle in the returned array. We use that to fetch info on the individual cURL request.If the link was dead or timed out, there will be no http code.If the link was a 404 page, the http code will be set to 404.Otherwise we assume it was a working link. (You may add additional checks for 500 error codes etc...)We remove the cURL handle from the multi handle since it is no longer needed, and close it.We can now add another url to the multi handle, and again do the initial work before moving on.Everything is finished. We can close the multi handle and print a report.This is the function that adds a new url to the multi handle. The static variable $index is incremented every time this function is called, so we can keep track of where we left off.I ran the script on my blog (with some broken links added on purpose, for testing), and here is what it looked like:It took only less than 2 seconds to go through about 40 URLs. The performance gains are significant when dealing with even larger sets of URLs. If you open ten connections at the same time, it can run up to ten times faster. Also you can just utilize the non-blocking nature of the multi curl handle to do URL requests without stalling your web script.Some Other Useful cURL OptionsHTTP AuthenticationIf there is HTTP based authentication on a URL, you can use this: $url = "http://www.somesite.com/members/";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// send the username and password curl_setopt($ch, CURLOPT_USERPWD, "myusername:mypassword");

// if you allow redirections curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // this lets cURL keep sending the username and password // after being redirected curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, 1);

$output = curl_exec($ch);

curl_close($ch); FTP UploadPHP does have an FTP library, but you can also use cURL: // open a file pointer $file = fopen("/path/to/file", "r");

// the url contains most of the info needed $url = "ftp://username:password@mydomain.com:21/path/to/new/file";

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// upload related options curl_setopt($ch, CURLOPT_UPLOAD, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_INFILESIZE, filesize("/path/to/file"));

// set for ASCII mode (e.g. text files) curl_setopt($ch, CURLOPT_FTPASCII, 1);

$output = curl_exec($ch); curl_close($ch); Using a ProxyYou can perform your URL request through a proxy: $ch = curl_init();

curl_setopt($ch, CURLOPT_URL,'http://www.example.com');

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

// set the proxy address to use curl_setopt($ch, CURLOPT_PROXY, '11.11.11.11:8080');

// if the proxy requires a username and password curl_setopt($ch, CURLOPT_PROXYUSERPWD,'user:pass');

$output = curl_exec($ch);

curl_close ($ch); Callback FunctionsIt is possible to have cURL call given callback functions during the URL request, before it is finished. For example, as the contents of the response is being downloaded, you can start using the data, without waiting for the whole download to complete. $ch = curl_init();

curl_setopt($ch, CURLOPT_URL,'http://net.tutsplus.com');

curl_setopt($ch, CURLOPT_WRITEFUNCTION,"progress_function");

curl_exec($ch);

curl_close ($ch);

function progress_function($ch,$str) {

echo $str; return strlen($str);

} The callback function MUST return the length of the string, which is a requirement for this to work properly.As the URL response is being fetched, every time a data packet is received, the callback function is called.ConclusionWe have explored the power and the flexibility of the cURL library today. I hope you enjoyed and learned from the this article. Next time you need to make a URL request in your web application, consider using cURL.Thank you and have a great day!Write a Plus TutorialDid you know that you can earn up to $600 for writing a PLUS tutorial and/or screencast for us? We're looking for in depth and well-written tutorials on HTML, CSS, PHP, and JavaScript. If you're of the ability, please contact Jeffrey at nettuts@tutsplus.com.Please note that actual compensation will be dependent upon the quality of the final tutorial and screencast.Follow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web.

]]>
Fri, 08 Jan 2010 11:05:10 -0800 http://www.federicobond.com.ar/items/view/476/techniques-for-mastering-curl
6 CodeIgniter Hacks for the Masters http://www.federicobond.com.ar/items/view/439/6-codeigniter-hacks-for-the-masters

CodeIgniter is a simple and powerful open source web application framework for PHP. Today, we’ll do some core “hacks” to this framework to change and improve its functionality. In the process, you’ll gain a better understanding of the intricacies of CodeIgniter.DisclaimerIt is not recommended to apply these hacks to an existing project. Since they change some of CodeIgniter’s core functionality, it can break the existing code.As of this writing, CodeIgniter 1.7.2 is the latest stable release. These hacks are not guaranteed to work for future (or past) releases.Even though CodeIgniter is designed to be PHP 4 compatible, some of these hacks are not. So you will need a server with PHP 5 installed.When you make any changes to the files inside the system folder, you should document it somewhere for future reference. Next time you upgrade CodeIgniter (even though they do not release updates very often), you may need to reapply those changes.1. Autoloading Models PHP 5 StyleThe GoalOn the left side, you see the regular way of loading a model in CodeIgniter, from within a Controller. After this hack, we will be able to create objects directly. The code is cleaner, and your IDE will be able to recognize the object types. This enables IDE features such as auto-complete, or previewing documentation.There are two more side effects of this hack. First, you are no longer required to extend the Model class:And you no longer have to add a require_once call before you do model class inheritance.The HackAll we need to do is add a PHP 5 style autoloader function.Add this code to the bottom of system/application/config/config.php: <?php // ...

function __autoload($class) { if (file_exists(APPPATH."models/".strtolower($class).EXT)) { include_once(APPPATH."models/".strtolower($class).EXT); } } ?> If you are interested in applying this hack for controllers too, you can use this code instead: <?php // ...

function __autoload($class) { if (file_exists(APPPATH."models/".strtolower($class).EXT)) { include_once(APPPATH."models/".strtolower($class).EXT); } else if (file_exists(APPPATH."controllers/".strtolower($class).EXT)) { include_once(APPPATH."controllers/".strtolower($class).EXT); } } ?> Any time you try to use a class that is not defined, this __autoload function is called first. It takes care of loading the class file.2. Prevent Model-Controller Name CollisionThe GoalNormally, you can not have the same class name for a Model and a Controller. Let’s say you created a model name Post: class Post extends Model {

// ...

} Now you can not have a URL like this: http://www.mysite.com/post/display/13 The reason is because that would require you to also have a Controller class named ‘Post.’ Creating such a class would result in a fatal PHP error.But with this hack, it will become possible. And the Controller for that URL will look like this: // application/controllers/post.php

class Post_controller extends Controller {

// ...

} Note the ‘_controller’ suffix.The HackTo get around this issue, normally most people add the ‘_model’ suffix to the Model class names (eg. Post_model). Model objects are created and referenced all over the application, so it might seem a bit silly to have all of these names with ‘_model’ floating around. I think it is better to add a suffix to the Controllers instead, since they are almost never referenced by their class names in your code.First we need to extend the Router class. Create this file: “application/libraries/MY_Router.php” class MY_Router extends CI_Router { var $suffix = '_controller';

function MY_Router() { parent::CI_Router(); }

function set_class($class) { $this->class = $class . $this->suffix; }

function controller_name() {

if (strstr($this->class, $this->suffix)) { return str_replace($this->suffix, '', $this->class); } else { return $this->class; }

} } Now edit “system/codeigniter/CodeIgniter.php” line 153: if ( ! file_exists(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT)) Same file, line 158: include(APPPATH.'controllers/'.$RTR->fetch_directory().$RTR->controller_name().EXT); Next, edit: “system/libraries/Profiler.php”, line 323: $output .= " ".$this->CI->router->controller_name()."/".$this->CI->router->fetch_method()."

"; That is all. Keep in mind that with this hack you are required to put the ‘_controller’ suffix on all of your controller class names. But not in the file names or the URL’s.3. Form Validation for Unique ValuesThe GoalCodeIgniter has a nice Form_validation class. It comes with several validation rules:These are useful, but there is an important one missing from this list: to check for unique values. For example, most user registration forms need to check that the username is not already taken, or the e-mail address is not already in the system.With this hack, you will be able add this validation rule to your form submission handler very easily: $this->form_validation->set_rules('username', 'Username', 'required|alpha_numeric|min_length[6]|unique[User.username]'); Note the last part that says “unique[User.username].” This new validation rule is just called “unique,” and takes a parameter inside the square brackets, which is “tablename.fieldname”. So it will check the “username” column of the “User” table to make sure the submitted value does not already exist.Similarly, you can check for duplicate e-mails: $this->form_validation->set_rules('email', 'E-mail', 'required|valid_email|unique[User.email]'); And your application can respond with proper error messages:The HackThis might be considered more of an extension than a hack. Nevertheless, we are going to take a core CodeIgniter library and improve it.Create: “application/libraries/MY_Form_validation.php” class MY_Form_validation extends CI_Form_validation {

function unique($value, $params) {

$CI =& get_instance(); $CI->load->database();

$CI->form_validation->set_message('unique', 'The %s is already being used.');

list($table, $field) = explode(".", $params, 2);

$query = $CI->db->select($field)->from($table) ->where($field, $value)->limit(1)->get();

if ($query->row()) { return false; } else { return true; }

} } Now you can use the unique validation rule.4. Running CodeIgniter from the Command LineThe GoalJust like the title says, our goal is to be able to run CodeIgniter applications from the command line. This is necessary for building cron jobs, or running more intensive operations so you don’t have the resource limitations of a web script, such as maximum execution time.This is what it looks like on my local Windows machine:The above code would be like calling this URL: http://www.mysite.com/hello/world/foo The HackCreate a “cli.php” file at the root of your CodeIgniter folder: if (isset($_SERVER['REMOTE_ADDR'])) { die('Command Line Only!'); }

set_time_limit(0);

$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = $argv[1];

require dirname(FILE) . '/index.php'; If you are on a Linux environment and want to make this script self executable, you can add this as the first line in cli.php:

!/usr/bin/php

If you want a specific controller to be command line only, you can block web calls at the controller constructor: class Hello extends Controller {

function __construct() { if (isset($_SERVER['REMOTE_ADDR'])) { die('Command Line Only!'); } parent::Controller(); }

// ...

} 5. Adding Doctrine ORM to CodeIgniterThe GoalDoctrine is a popular Object Relational Mapper for PHP. By adding it to CodeIgniter, you can have a very powerful Model layer in your framework.The HackJust installing Doctrine is not very “hacky” per se, as we can just add it as a plug-in. However, once added, your Model classes will need to extend the Doctrine base classes, instead of the CodeIgniter Model class. This will completely change the way the Model layer works in the framework. The objects you create will have database persistence and also will able to have database relationships with other objects.Follow these steps:Create folder: application/pluginsCreate folder: application/plugins/doctrineDownload Doctrine (1.2 as of this article)Copy the “lib” folder from Doctrine to: “application/plugins/doctrine”Create “application/plugins/doctrine_pi.php” // system/application/plugins/doctrine_pi.php

// load Doctrine library require_once APPPATH.'/plugins/doctrine/lib/Doctrine.php';

// load database configuration from CodeIgniter require_once APPPATH.'/config/database.php';

// this will allow Doctrine to load Model classes automatically spl_autoload_register(array('Doctrine', 'autoload'));

// we load our database connections into Doctrine_Manager // this loop allows us to use multiple connections later on foreach ($db as $connection_name => $db_values) {

// first we must convert to dsn format $dsn = $db[$connection_name]['dbdriver'] . '://' . $db[$connection_name]['username'] . ':' . $db[$connection_name]['password']. '@' . $db[$connection_name]['hostname'] . '/' . $db[$connection_name]['database'];

Doctrine_Manager::connection($dsn,$connection_name); }

// CodeIgniter's Model class needs to be loaded require_once BASEPATH.'/libraries/Model.php';

// telling Doctrine where our models are located Doctrine::loadModels(APPPATH.'/models'); Next, edit “application/config/autoload.php” to autoload this Doctrine plugin $autoload['plugin'] = array('doctrine'); Also make sure you have your database configuration in “application/config/database.php”.That is all. Now you can create Doctrine Models within your CodeIgniter application. Read my tutorials on this subject for more information.6. Running Multiple SitesThe GoalThis hack will make it possible for you to run multiple sites from a single install of CodeIgniter. Each website will have its own application folder, but they will all share the same system folder.The HackInstall CodeIgniter anywhere on the server. It doesn’t need to be under a website folder. Then take the application folder out of the system folder. And make additional copies of it, as seen in the image above, for every website you want to run. You can place those application folders anywhere, like under each separate website folders.Now copy the index.php file to the root of each website folder, and edit it as follows:At line 26, put the full path to the system folder: $system_folder = dirname(FILE) . '../codeigniter/system'; At line 43, put the full path to the application folder: $application_folder = dirname(FILE) . '../application_site1'; Now you can have independent websites using separate application folders, but sharing the same system folder.There is a similar implementation in the CodeIgniter User Guide you can read also.7. Allowing All File Types for UploadsThe GoalWhen using the Upload library in CodeIgniter, you must specify which file types are allowed. $this->load->library('upload');

$this->upload->set_allowed_types('jpg|jpeg|gif|png|zip'); If you do not specify any file types, you will receive an error message from CodeIgniter: “You have not specified any allowed file types.”So, by default, there is no way to allow all file types to be uploaded. We need to do small hack to get around this limitation. After that we will be able to allow all file types by setting it to ‘*’. $this->load->library('upload');

$this->upload->set_allowed_types('*'); The HackFor this hack we are going to modify the Upload class behavior.Create file: application/libraries/My_Upload.php class MY_Upload extends CI_Upload {

function is_allowed_filetype() {

if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types)) { $this->set_error('upload_no_file_types'); return FALSE; }

if (in_array("*", $this->allowed_types)) { return TRUE; }

$image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');

foreach ($this->allowed_types as $val) { $mime = $this->mimes_types(strtolower($val));

// Images get some additional checks if (in_array($val, $image_types)) { if (getimagesize($this->file_temp) === FALSE) { return FALSE; } }

if (is_array($mime)) { if (in_array($this->file_type, $mime, TRUE)) { return TRUE; } } else { if ($mime == $this->file_type) { return TRUE; } } }

return FALSE;

}

} ConclusionI hope some of these are useful to you. If not, they are still interesting to know and can help you learn more about the internal workings of a framework and some of the core PHP language features.If you know any other cool hacks or modifications, let us know in the comments. Thank you!Follow us on Twitter, or subscribe to the Nettuts+ RSS Feed for the best web development tutorials on the web. ReadyReady to take your skills to the next level, and start profiting from your scripts and components? Check out our sister marketplace, CodeCanyon.

]]>
Tue, 29 Dec 2009 08:50:14 -0800 http://www.federicobond.com.ar/items/view/439/6-codeigniter-hacks-for-the-masters
Getting started with CSScaffold http://www.federicobond.com.ar/items/view/328/getting-started-with-csscaffold

CSScaffold is a script written in PHP that parses your CSS files transparently (by using a .htaccess file), to extend the capabilities of vanilla css.

It lets you use nested selectors, constants, expressions and mixins all while using the standard CSS syntax.

It has an easy-to-use plugin system, so you can add your own features (you might want to set constants via xml so a CMS can integrate with your CSS).

Get the latest Scaffold here - github.com/anthonyshort/csscaffold/tree/master

Documentation - wiki.github.com/anthonyshort/csscaffold Cast: Anthony Short

]]>
Sun, 22 Nov 2009 18:29:51 -0800 http://www.federicobond.com.ar/items/view/328/getting-started-with-csscaffold
Getting started with CSScaffold http://www.federicobond.com.ar/items/view/175/getting-started-with-csscaffold

CSScaffold is a script written in PHP that parses your CSS files transparently (by using a .htaccess file), to extend the capabilities of vanilla css.

It lets you use nested selectors, constants, expressions and mixins all while using the standard CSS syntax.

It has an easy-to-use plugin system, so you can add your own features (you might want to set constants via xml so a CMS can integrate with your CSS).

Get the latest Scaffold here - github.com/anthonyshort/csscaffold/tree/master

Documentation - wiki.github.com/anthonyshort/csscaffold Cast: Anthony Short

]]>
Sun, 22 Nov 2009 18:29:00 -0800 http://www.federicobond.com.ar/items/view/175/getting-started-with-csscaffold
Code Snippets http://www.federicobond.com.ar/items/view/202/code-snippets ]]> Fri, 30 Oct 2009 16:51:00 -0700 http://www.federicobond.com.ar/items/view/202/code-snippets The Definitive Guide to htaccess Techniques: Do’s and Don’ts http://www.federicobond.com.ar/items/view/29/the-definitive-guide-to-htaccess-techniques-dos-and-donts

Of all the elements of web design and coding, htaccess can be one of the most intimidating. After all, it’s an incredibly powerful tool and one that has the potential to completely break your site if you’re not careful. Below are a dozen basic htaccess techniques and tips to get you started. They’re not nearly as intimidating as many people expect, and if you study the code for a few minutes, I’m sure you’ll quickly grasp exactly how they work and why. After that are a few bewares and don’ts for working with htaccess to help keep you out of trouble, and some more resources for further working with htaccess.

12 Basic htaccess Tips: 1. Create a custom error page. .htaccess on a Linux Apache server makes it easy to create your own custom error pages. Just create your custom error page files and then add this code to your .htaccess file: ErrorDocument 401 /401.php ErrorDocument 403 /403.php ErrorDocument 404 /404.php ErrorDocument 500 /500.php

(Obviously you should replace the “/500.php” or whatever with your own file path and name.) 2. Prevent directory browsing. If you don’t include an index file in a directory, visitors can browse the directory itself. But preventing that is as easy as adding a single line to your .htaccess file:

Options All -Indexes

  1. Set the default page of each directory. If you don’t want to use an index page in each directory, you can set the default page visited when someone reaches (like an about page or a page offering the newest content) that directory by adding this:

DirectoryIndex news.html

(And of course you’d replace the “news.html” bit with whatever you want to use as the default.) 4. Set up a 301 redirect. If you move around the structure of your site and need to redirect some old URLs to their new locations, the following bit of code will do so for you:

Redirect 301 /original/filename.html http://domain.com/updated/filename.html

  1. Compress file output with GZIP. You can add the following code to your htaccess file to compress all of your JavaScript, CSS and HTML files using GZIP.

<IfModule mod_gzip.c> mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file .(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude mime ^image.* mod_gzip_item_exclude rspheader ^Content-Encoding:.gzip. </IfModule>

  1. Redirect to a secure https connection If you want to redirect your entire site to a secure https connection, use the following:

RewriteEngine On RewriteCond %{HTTPS} !on RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

  1. Block script execution. You can stop scripts in certain languages from running with this:

Options -ExecCGI AddHandler cgi-script .pl .py .php .jsp. htm .shtml .sh .asp .cgi

Just replace the types of scripts you want to block. 8. Force a file to download with a “Save As” prompt. If you want to force someone to download a file instead of opening it in their browser, use this code:

AddType application/octet-stream .doc .mov .avi .pdf .xls .mp4

  1. Restrict file upload limits for PHP. You can restrict the maximum file size for uploading in PHP, as well as the maximum execution time. Just add this:

php_value upload_max_filesize 10M php_value post_max_size 10M php_value max_execution_time 200 php_value max_input_time 200

Line one specifies the maximum file size for uploading; line two is the maximum size for post data; line three is the maximum time in seconds a script can run before it’s terminated; and line four is the maximum amount of time in seconds a script is allowed to parse input data. 10. Enable File Caching. Enabling file caching can greatly improve your site’s performance and speed. Use the following code to set up caching (changing the file types and time values to suit your site’s needs):

cache html and htm files for one day

<FilesMatch ".(html|htm)$"> Header set Cache-Control "max-age=43200" </FilesMatch>

cache css, javascript and text files for one week

<FilesMatch ".(js|css|txt)$"> Header set Cache-Control "max-age=604800" </FilesMatch>

cache flash and images for one month

<FilesMatch ".(flv|swf|ico|gif|jpg|jpeg|png)$"> Header set Cache-Control "max-age=2592000" </FilesMatch>

disable cache for script files

<FilesMatch ".(pl|php|cgi|spl|scgi|fcgi)$"> Header unset Cache-Control </FilesMatch>

(Time shown for max-age is in seconds.) 11. Protect your site from hotlinking. The last thing you want is for those stealing your content to also be able to embed the images hosted on your server in their posts. It takes up your bandwidth and can quickly get expensive. Here’s a way to block hotlinking within htaccess:

RewriteEngine On RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !^http://([ -a-z0-9] .)?domain.com [NC] RewriteRule .(gif|jpe?g|png)$ - [F,NC,L]

(Of course you’ll want to replace the domain.com with your own domain name.) 12. Disguise your file types. You can disguise all of your file types by making them appear as PHP files. Just insert this snippet in:

ForceType application/x-httpd-php

8 Common htaccess Mistakes and Don’ts:

Be careful of spelling- .htaccess is not forgiving of spelling errors. htaccess is case sensitive. If something is shown in the examples with a capital letter, make sure it’s capitalized in your htaccess file. Consider your caching needs carefully before setting it up. If your site is almost entirely static, you can set longer cache times. If your site changes daily, make sure you adapt which files will cache for how long. There’s nothing worse as a visitor than coming back to a site thinking there’s been an update and not seeing it. Don’t forget to comment out your notes within the file. This is done by adding a # before the comment line. Always test your site immediately after making any changes to your htaccess file. One mistyped character could make the difference between your site working and being down for hours before you realize what’s happened. On that note, always make sure you backup your htaccess file before making any changes. That way, if there is a problem, you can easily swap back in the old file. Make sure any essential htaccess functions you’ve included are cross-browser compatible. There are certain things some browsers just won’t support (one example is with certain methods for forcing file downloads). Remember when protecting a web directory with htaccess, that unless it’s restricted to https access, the password could be sniffed (as your authentication will be done over an un-secure connection).

More Resources:

21 Very Useful htaccess Tips and Tricks 5 htaccess Tricks Every Webmaster Should Know 16 Useful .htaccess Tricks and Hacks For Web Developers Stupid htaccess Tricks Using htaccess Files for Pretty URLS

Author: Cameron Chapman Cameron Chapman is a writer, blogger, copyeditor, and social media addict. She’s been designing for more than six years and writing her whole life. If you’d like to connect with her, you can follow her on Twitter or at her Personal Website.

Write for Us! We are looking for exciting and creative articles, if you want to contribute, just send us an email.

]]>
Sun, 09 Aug 2009 14:47:00 -0700 http://www.federicobond.com.ar/items/view/29/the-definitive-guide-to-htaccess-techniques-dos-and-donts