Run CodeIgniter from the Command Line / SSH

Published: December 1st, 2008 by: Andrew

CodeIgniter has quickly become my favorite framework to use when coding applications in PHP. CodeIgniter makes it way too easy to follow the MVC approach, maintain modulated code, and to also have access to several additional helpers and libraries. But, there was one major flaw: The inability to easily access via the command line. Why? Because CI uses the Request URI to route to the controllers. That is a webserver-specific function, and is not available from the command line. There are other solutions to this problem out there, but I think mine is better.


Why?

A lot of my web projects require import scripts and other actions that maintain the website outside the typical http request. Most of the time, I use the CLI requests in “cron jobs”, or scripts that are automatically called at certain times by the server operating system. CodeIgniter doesn’t have this CLI (command line) functionality, so we have to add it in ourselves.

Existing/Old Method

In order to call a controller/method from the command line, a separate file needs to be created that manually sets the REQUEST_URI / PATH_INFO, which are both Apache environment variables and are used by CI to determine which controller & method to call up. We need to set those items as well as a couple others to make this possible. Time for an example:

#!/usr/bin/php
<?php
/* make sure this isn't called from a web browser */
if (isset($_SERVER['REMOTE_ADDR'])) die('Permission denied.');

/* set the controller/method path */
$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = '/cron/purge_cache';

/* raise or eliminate limits we would otherwise put on http requests */
set_time_limit(0);
ini_set('memory_limit', '256M');

/* call up the framework */
include(dirname(__FILE__).'/index.php');
?>

Since this is a shell script, we need the hashbang (line 1 of the example). This tells Bash what binary to use to run the script. If /usr/bin/php is not the path to PHP in your environment, you will need to change that accordingly.

Line 4 simply detects whether it is being ran through a shell session or browser. If we have a remote IP address, then it was accessed by a web browser.

Line 7 is the import one. This sets the controller/method to call. In my example, we are calling the ‘cron’ controller and the ‘purge_cache’ method.

There is one big problem with this method. We have to create a file for every possible command line request. As you can see, the requested controller/method is hard-coded in the file. This method works ok when we only have one or two things callable by the command line, but usually, I have more than that.

My New Method

PHP has the option to accept command-line arguments, and we are going to use that to our advantage. For my solution, I created a copy of index.php, and saved it as cli.php. I then added the following code to the top of the file:

#!/usr/local/bin/php
<?php

/**
 * only a few lines of code will make the best web framework
 * function on the command line
 */

	/* we don't need to be limited by...normal limitations */
	set_time_limit(0);
	ini_set('memory_limit', '256M');

	/* make sure this isn't being called by a web browser */
	if (isset($_SERVER['REMOTE_ADDR'])) die('Permission denied.');

	/* set some constants */
	define('CMD', 1);

	/* manually set the URI path based on command line arguments... */
	unset($argv[0]); /* ...but not the first one */
	$_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = '/' . implode('/', $argv) . '/';

Lines 10-14 should look familiar, as I took that form the previous example. Line 17 is just a constant that we will use later.

I am using PHP’s $argv variable to get a list of the arguments, so if I called the script by saying:

./cli.php cron purge_cache

…then $argv would be an array with the elements of ‘cli.php’, ‘cron’, ‘purge_cache’. I don’t need the first element, so I unset that in line 20. Then I implode the rest of the elements to form the request path, and the end result would be /cron/purge_cache/, just like our hard-coded example at the beginning.

By doing it this way, we created another handler that can run any controller on the command line w/o modifications. The only thing we needed was a handler for CLI requests.

A Step Further – Custom Error Pages

CodeIgniter’s error pages have HTML that we don’t need to see in our command line. I defined that CMD constant so I can tell if we are accessing via the CLI or web browser. The following bit of code is my 404 error page found at ./system/application/errors/error_404.php. I use some conditionals and show a plain-text form of the error if viewed in the CLI.

<?php header("HTTP/1.1 404 Not Found"); ?>
<?php if(defined('CMD')) : ?>

***<?php echo $heading; ?>***

<?php echo $message; ?>

<?php else : ?>
<html>
<head>
<title>404 Page Not Found</title>
<style type="text/css">

body {
background-color:	#fff;
margin:				40px;
font-family:		Lucida Grande, Verdana, Sans-serif;
font-size:			12px;
color:				#000;
}

#content  {
border:				#999 1px solid;
background-color:	#fff;
padding:			20px 20px 12px 20px;
}

h1 {
font-weight:		normal;
font-size:			14px;
color:				#990000;
margin: 			0 0 4px 0;
}
</style>
</head>
<body>
	<div id="content">
		<h1><?php echo $heading; ?></h1>
		<?php echo $message; ?>
	</div>
</body>
</html>
<?php endif; ?>

So there you have it, following these steps will turn one of the best PHP frameworks into a CLI-capable wonder. Please feel free to post a comment if you have a question or anything to add.

For more information on using PHP from the command line, see http://us.php.net/features.commandline.

Related posts:

21 Responses to “Run CodeIgniter from the Command Line / SSH”

  • Jacob Sandoval

    Thanks for this code it worked great.

     

  • Andrew

    Sweet, glad to hear it worked for you. :)

     

  • Philip Sturgeon

    Nice solution, I was first but seems your way is better.

    http://codeigniter.com/wiki/CI_on_the_command_line/

    I was using a URI extension to get this going, but using a different index file gives you much greater flexibility.

    For more CLI command line fun that will work with both methods, check out my CLI library:

    http://codeigniter.com/forums/viewthread/99498/

     

  • Andrew

    Hi Philip. I wonder why I didn’t find your Wiki page on CI’s site before I wrote this article, because I was looking for alternate methods. If the timestamps are anything to go by, it looks like your pages were posted after this one.

    I like your library – I’ve only had to use the shell to call up cron jobs, so no input was necessary, but I will have to keep your library in mind if I need to in the future. Thanks for sharing. :)

     

  • Joel

    Great, just what I was looking for.

    Much smarter than other solutions I’ve seen which seem to think doing a GET request to the actual url is a decent way to do it ;)

     

  • Andrew

    For some server environments that has shell disabled, something like wget is the only way, but I agree with you – it’s a bad way to do it. Why involve Apache, etc when it’s not needed…

     

  • Rad

    This really useful method of implementing CLI with CI applications.

    I was investigating CI URI extension but this simple but very smart approach proofed to be better solution if CLI needs to be plumbed into quite complex existing CI application.

    Thanks

     

  • Gresham

    Hi Andrew, This looks very interesting to me as i have been searching for a way to load a joomla page with its parameters from the cron job panel. If my understanding is correct is this what i am supposed to do:

    1. Make a copy of the index.php file and rename to whatever you want to call it via the cron job. (eg index2.php)
    2. add the code above to the top of the php file.
    3. in the cron job my command will look like this
    /usr/local/bin/php -q /home/mysite/public_html/dwaf2/index2.php option=com_projectfork Itemid=53

    is the above correct ?

     

  • Andrew

    Rad: Glad it is a help to you.

    Gresham: not exactly. Codeigniter is setup in a way that there are no variable=value pairs in the URL (more on that here), they are just values. Also, there are no values for the controller & method to be called. so in your case the url would be:

    /usr/local/bin/php -q /home/mysite/public_html/dwaf2/index2.php controller method com_projectfork 53

    …and that block of code above would call up the URI…

    /controller/method/com_projectfork/53

    So we should know that the option is going to be found in $this->uri->segment(3), and the Itemid is in $this->input->segment(4). If you need the variable=value pairs to manually build a _GET array, then the code will have to be modified a bit. You will have to change it up anyway to get it to work with Joomla, as I’m not sure how the URI routing works for Joomla. Hope this helps.

     

  • Gresham

    Thanks for the direction Andrew, i guess i have to keep working to get what i need, but a huge thank u to you for pointing me in the right direction. If you do come across a solution for me please let me know as my php skills are very limited but im willing to learn.

     

  • Chris

    Good solution, thanks a lot. I agree there is no reason to use http when it’s not necessary.

     

  • Greg

    Hi Andrew, I’m having a trouble setting up CI like this. I’m actually able to run the file, but for some reason, the cli.php is failing around like 115 where “APPPATH” is defined. The is_dir($application_folder) is returning false, so instead of finding the apppath in the current directory, it’s getting into the else and is being defined as:

    BASEPATH.$application_folder.’/’

    My CI exists outside the root, so my application folder is in the same directory as the cli.php. The problem is then all the includes are getting the wrong paths, etc…

    Any idea how to fix this? Thanks for your post!

    Greg

     

  • Greg

    Hi Andrew, another question I’m hoping you might have run into. When your running codeigniter via the command line (I finally got mine set up :) , how do you make CI connect to a database? I’m having issues connecting when running it via command line and thought maybe you had run into this before.

    Thanks!

    Greg

     

  • Andrew

    Greg: Yeah, I had this same issue. I’m guessing that you are using relative paths. The problem is that when you call it from the command line, the base path is your current working directory (‘pwd’). So, it may work when you call it from a terminal, but then not work when you call it from the cron tab or other means. The best way to work around it is to use the full path to the application directory. That way, you can call it from any directory, and it will still work.

    As far as the database connection, it’s just like a normal setup. No extra configuration should be needed there.

     

  • Greg

    Hey Andrew! Got my issues figured out. Your post was a life saver as before I was running my cron via wget with some rather insecure “validation” methods to see if it was cron. This way is much more secure and works great. Thanks for the post!

     

  • Mohsen

    Hi Andrew,
    Thanks for this smart solution for accessing CI via Command Line. Works great :)

    The only problem I had is that although I edited my 404 error page I still could access my cron’s controller/function via a web browser!

    Here is what I’ve done to fix it, in the controller’s constructor I implemented:

    if(!defined(‘CMD’)) {
    exit;
    }

    That’s all.

     

  • Joel

    Great job! Quick bonus thought for you — instead of copying index.php and adding your code to the top, just make cli.php have ONLY your code followed by “include(‘index.php’);”. This gives you one of two benefits:

    - if/when CI puts out an update, you don’t have to worry about whether or not you remembered to updated cli.php as well (though obviously you’d want to test it well)

    - if you have any need to modify index.php itself at all you don’t have to worry about making sure you keep cli.php in sync.

    Clearly they’re conflicting benefits (you can’t hack index.php and still expect to just drop in any upstream updates), but they’re both based on the same principle: don’t keep duplicate code lying around if you don’t have to.

     

  • 40+ CodeIgniter Framework Tutorials for Kick-Ass PHP Application | PHP Frameworks

    [...] 42. Run CodeIgniter from the Command Line / SSH [...]

     

  • Andrew

    There may be an issue with this solution when it comes to logging – specifically the permissions related to the log files.

    Log files are created with the permissions of the user running the script. Depending on your hosting environment php files may be run either as the webserver (eg. www, apache, etc) or my run as your actual personal user.

    If scripts executed by apache (litespeed, IIS, etc) are not run using your personal account then they are going to create log files that are owned by them (even though you can obviously read them).

    If your cron script runs at midnight then it may create the log file before any scripts have been run by the web server. Depending on your umask settings you may inadvertently block the web server from being able to write to these files.

    Also, the CI Log file tries to do a CHMOD on the log file every time it write to it (not sure why but it does). If it doesn’t own the file then this will cause a warning to be thrown that will probably clutter up your log files.

    In short, it’s probably best to use different log files for cron scripts and web scripts. This is easily done using the CMD variable that you have defined.

    In config.php you can have:
    $config['log_path'] = defined(‘CMD’) ? ‘/some/directory/logs/cron/’ : ‘/some/directory/logs/’;

     

  • Dave

    Anyone have database connection issues with CLI and CI? I am using OSX and MAMP, and wondering if it’s something there I need to hack at. I am telling CI to use “localhost” instead of sockets, as the socket method just hangs in a loop.

     

  • Dave

    SOLVED! My post from last night is now solved. Instead of connecting via “localhost”, I use “:/Applications/MAMP/tmp/mysql/mysql.sock”. Note the use of the leading colon, something I missed in the configuration. This socket works from the CLI and a Browser.

    Thanks for this wonderful tip Andrew!

     

Leave a Reply





Wordpress doesn't like it when you post PHP code. Go save your code at pastebin, and post the link here.

About the Author

Andrew has been coding PHP applications for over 5 years, and has plenty of experience with PHP, MySQL, and Apache. He prefers Ubuntu Linux on his desktop and has plenty of experience at managing CentOS web servers. He is the owner of Wells IT Solutions LLC, and develops PHP applications full time for anyone that needs it as well as does desktop computer support locally in the local area. He spends most of his free time exploring new programming concepts and posting on The Webmaster Forums.