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['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.
Jacob Sandoval
Dec 9th, 2008
3:46 pm
Thanks for this code it worked great.
Andrew
Dec 9th, 2008
3:53 pm
Sweet, glad to hear it worked for you. 🙂
Philip Sturgeon
Jan 7th, 2009
6:22 am
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
Jan 7th, 2009
8:36 am
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
Jan 8th, 2009
3:24 am
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
Jan 8th, 2009
12:24 pm
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
Jan 19th, 2009
5:59 am
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
Feb 16th, 2009
2:11 am
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
Feb 16th, 2009
8:16 am
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
Feb 16th, 2009
10:07 am
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
May 11th, 2009
1:37 pm
Good solution, thanks a lot. I agree there is no reason to use http when it’s not necessary.
Greg
Jul 17th, 2009
4:34 pm
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
Jul 18th, 2009
5:08 am
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
Jul 18th, 2009
8:00 am
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
Jul 19th, 2009
2:01 pm
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
Jul 21st, 2009
11:26 pm
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
Jul 24th, 2009
2:37 pm
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.
Oct 16th, 2009
4:05 am
[…] 42. Run CodeIgniter from the Command Line / SSH […]
Andrew
Oct 17th, 2009
1:43 am
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
Nov 23rd, 2009
9:59 pm
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
Nov 24th, 2009
6:20 am
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!
Apr 20th, 2010
2:07 pm
[…] Run CodeIgniter from the Command Line / SSH […]
Jacob Gourd
Jun 15th, 2010
7:31 am
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
Jun 25th, 2010
7:29 am
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
Jun 25th, 2010
8:19 am
@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
Aug 13th, 2010
11:30 pm
Andrew,
Thanks for posting this. Saved me a lot of time and trouble!
Dave
JohnJohn
Oct 2nd, 2010
8:54 pm
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
Oct 11th, 2010
7:15 pm
Hi John –
REMOTE_ADDR is an index of the $_SERVER array. The condition should be:
isset($_SERVER[‘REMOTE_ADDR’])
Eric
Dec 7th, 2010
8:17 am
Why don´t you just run the cron via wget?
Andrew
Feb 17th, 2011
10:31 am
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
May 11th, 2011
10:58 pm
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
May 11th, 2011
11:11 pm
Andrew disregard my prior msg – it was solved deleting an empty line…
:oP
Terri Ann
Jun 22nd, 2011
8:39 am
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
Aug 9th, 2011
12:29 am
nice! it works, thank you for sharing.
Sam Henry
Jan 14th, 2012
8:04 pm
Worked perfectly for me too after including the index file as suggested by Terri Ann. Thanks all.
Dharmesh
Jan 14th, 2013
10:16 am
hi, where can I place Cli.php in Codeigniter? Thanks in advance.
Andrew
Jan 14th, 2013
10:23 am
I renamed the file from index.php in my environment. Sorry – should have mentioned that. 🙂
Dharmesh
Jan 14th, 2013
10:43 am
Hay, thanks. one more question. Do i need to combine both scripts you have given?? Cheers 🙂
Andrew
Jan 14th, 2013
1:25 pm
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
May 30th, 2013
8:29 am
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
Thomas
Oct 27th, 2014
5:16 am
For anyone having the problem that no output is coming back when running a controller action from shell.
I had the same problem, but I extended the CI_Controller with my own core controller, checking if the user is logged in, before continuing.
That was my problem 🙂
So if you don’t get output and have your own core controller try to extend your controller with CI_Controller and see if you get output, if yes your problem is probably somewhere in your core controller
Mike
Mar 11th, 2015
10:03 am
Amusingly this works with PyroCMS (v2.2 built on CI) with some errors. PyroCMS v3 has moved to Laravel, so this will likely no longer work with the newer versions.
It complains about SERVER_NAME and SERVER_PORT, but it does run successfully.
Phil’s link is now dead and the CI docs on CLI does not work due to extended controllers… so this is the only thing I’ve found so far that will run successfully for my specific environment.