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:

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.

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.

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

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:

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.

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.

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…

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.

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.

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!

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.

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.

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.

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.

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

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)

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!

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>

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

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.

Leave a Reply

Name (required)

Mail (will not be published) (required)

Website

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

Notify me of followup comments via e-mail

Notify me of new posts by email.

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.