Run CodeIgniter from the Command Line / SSH

Published: December 1st, 2008 by:

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['QUERY_STRING'] =  $_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.


40 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!

     

  • 30 Top Codeigniter best Tutorials You must want to know. | 99Points

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

     

  • Jacob Gourd

    Thanks for the great post.  It did not work for me first off.  Because I have the following set in my config.php
    $config['uri_protocol']    = “QUERY_STRING”;

    To make this work I changed

    $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = ‘/’ . implode(‘/’, $argv) . ‘/’;

    to

    $_SERVER['QUERY_STRING'] =  $_SERVER['PATH_INFO'] = $_SERVER['REQUEST_URI'] = ‘/’ . implode(‘/’, $argv) . ‘/’;

    You may like to change this so that it works for more people :o)  Thanks Again.

     

  • Kyle Bragger

    Awesome. Something I noticed when using this in conjunction with crontab: you’ve got to explicitly set the absolute path to your system and application folders, else this might not run if you’re not running from your app’s base directory.
    Here’s the fix; it’s only an extra line: https://gist.github.com/0affe07a5d6725721b82

     

  • Andrew

    @Jacob – Changed – thanks!

    @Kyle – Good catch.  I have had path issues with running this in some environments, and a solution just like yours has worked for me as well in the past.

     

  • Dave

    Andrew,
    Thanks for posting this.  Saved me a lot of time and trouble!
    Dave

     

  • JohnJohn

    Hi! I really like this method. The problem I am having is that isset(REMOTE_ADDR) is returning FALSE. my command line is ./public_html/cli.php cron alert

     

  • Andrew

    Hi John -

    REMOTE_ADDR is an index of the $_SERVER array.  The condition should be:

    isset($_SERVER['REMOTE_ADDR'])

     

  • Eric

    Why don´t you just run the cron via wget?

     

  • Andrew

    Eric, I think that would be more of a workaround.  It involves Apache in making a web request when it’s not necessary.  That and not all shared hosting accounts have access to wget.

     

  • Martin

    Hey Andrew thanks for the code – I understand this is a relatively old post but I wonder if you could give me some advice – I got a cron job to work using your code but in addition to the scheduled e-mail (via cron) I get the Daemon sending me a warning — any ideas? Thanks (using CI 2.0.2, PHP 5.2.14)

    Message:  Cannot modify header information – headers already sent by (output started at /chroot/home/username/example.com/html/cli.php:3)
    Filename: libraries/Session.php
    Line Number: 671

     

  • Martin

    Andrew disregard my prior msg – it was solved deleting an empty line…
    :oP

     

  • Terri Ann

    Thanks so much for sharing this!  It worked perfectly and much faster as the hacked wget calls I was using as a work around until I came across a better solution.
    I used your newest cli.php file but included the index file using

    include(dirname(__FILE__).'/index.php');
    As you had done in your earlier example.  Great addition with teh constant now I can go and secure all those calls that should only be accessed from the CLI and update my site to have the CLI trigger a fresh cache of heavy queries and pages that I’m already caching with my fork of Phil Sturgeon’s original cache library.  I’ll be comitting an update for it shotly to account for the constant defined in the cli.php file https://github.com/terriann/codeigniter-cache
    Thanks again!

     

  • onin

    nice! it works, thank you for sharing.

     

  • Sam Henry

    Worked perfectly for me too after including the index file as suggested by Terri Ann. Thanks all.

     

  • Dharmesh

    hi, where can I place Cli.php in Codeigniter? Thanks in advance.

     

  • Andrew

    I renamed the file from index.php in my environment. Sorry – should have mentioned that. :)

     

  • Dharmesh

    Hay, thanks. one more question. Do i need to combine both scripts you have given?? Cheers :)
     

     

  • Andrew

    Actually let me revise my previous comment…I made a copy of index.php and saved is as cli.php in the same directory. Sorry if I confused you.

    Also, this was for an older version of CI. This method should still work, but there is now a slightly easier method found in the user guide: http://ellislab.com/codeigniter/user-guide/general/cli.html

    Basically all you do is add the code in the “My New Method” to the cli.php file that is created.

     

  • Matej

    Hi,
     
    I tried your solution, but I have problem. I am using extended lang library, so it redirect http://www.mywebpage.com/controller/method to http://www.mywebpage.com/lang/controller/method.
    If lang doesn’r exist it change uri and send headers to ne url. So when I try to do cron it get me an error
    <h4>A PHP Error was encountered</h4>
    <p>Severity: Warning</p>
    <p>Message:  Cannot modify header information – headers already sent by (output started at /var/www/vaje2013/TPO1/system/core/Exceptions.php:185)</p>
    <p>Filename: core/MY_Lang.php</p>
    <p>Line Number: 75</p>
     
    Any suggestions how to prevent this?
     
    Thanks, Matej

     

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 since 2006, 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.