Calling controller actions from cron and the command line

By Matt Attlee (mattattlee)
This is a very simple tutorial that shows how you can modify the dispatcher to call controller actions from the command line and cron.
On the Google Group there has been quite a few threads about the best way to run cron jobs when using CakePHP. This is something I had do for my one of projects and I figured out a very simple way to do it by modifying the dispatcher so controller actions - such as an action to send out a batch of emails - can be invoked from the command line.

It's very simple. What you do is make a copy of index.php in
/app/webroot/, call it cron_dispatcher.php and place it in /app.

Scroll down in the code till you come to the line:-

Download code
require CORE_PATH.'cake'.DS.'bootstrap.php';

Now replaces everything below it with the following code:-

Download code
// Dispatch the controller action given to it
// eg php cron_dispatcher.php /controller/action
define('CRON_DISPATCHER',true);
if($argc == 2) {
        $Dispatcher= new Dispatcher();
        $Dispatcher->dispatch($argv[1]);
}

What this means is you can call controller actions from the command
line, so for example to call the send action in your mailouts
controller you just create a cron job like this:

php cron_dispatcher.php /mailouts/send

As you can see it's a very simple approach that doesn't break the MVC framework.

Since you are invoking your cron jobs as controller actions it is possible for users to invoke the action from the site proper. If there are certain actions that you don't want users to invoke add a line to the action to check that the constant CRON_DISPATCHER is defined. For example

Download code
function send() 
{
    // Check the action is being invoked by the cron dispatcher
    if (!defined('CRON_DISPATCHER')) { $this->redirect('/'); exit(); }

    $this->layout = null; // turn off the layout
    
    // do something here
}

 

Comments 176

CakePHP Team Comments Author Comments
 

Comment

1 Protecting this from the public

You could also
1) put the cron_dispatcher.php in a directory like /usr/local/bin, just make sure that the ROOT and APP_DIR constants are defined correctly.
2) pass an optional parameter as a password, like this:
php cron_dispatcher.php mysecretpassword /mailouts/send

then the code would become this:
[code php] define('MY_SECRET_PASSWORD','mysecretpassword');
if(($argc == 3) && $argv[1] == MY_SECRET_PASSWORD) {
$Dispatcher= new Dispatcher();
$Dispatcher->dispatch($argv[2]);
PS: i wouldn't replace *everything* below require CORE_PATH.'cake'.DS.'bootstrap.php';

this part still remains useful:
[code php] if (DEBUG) {
echo "";
}
Posted Dec 6, 2006 by Dieter Plaetinck
 

Bug

2 Didnt seem to work

I discovered some errors with loadController where it was not picking up my app directory (in 1.1.11) so i added an extra define and fixed he args:

require CORE_PATH . 'cake' . DS . 'bootstrap.php';
define ('APP', ROOT.DS.APP_DIR.DS.'app'.DS);
define('CRON_PASS','bjsf');
/**
* Call dispatcher
**/
if(($argc == 3) && $argv[1] == CRON_PASS) {
$Dispatcher= new Dispatcher();
$Dispatcher->dispatch($argv[2]);
}

if (Configure::read() > 0) {
echo "";
}


usage is the same:
php cron_dispatcher.php bjsf /workflows/check_timed_events
Posted Dec 19, 2006 by stab
 

Comment

3 Tutorial updated

I've made a minor update to the tutorial to give an example of how you can make controller actions only invokable by the cron dispatcher.

You could also
1) put the cron_dispatcher.php in a directory like /usr/local/bin, just make sure that the ROOT and APP_DIR constants are defined correctly.
2) pass an optional parameter as a password, like this:
php cron_dispatcher.php mysecretpassword /mailouts/send

Thanks for the feedback. In terms of the password parameter, it would add extra security but it wouldn't be necessary if the cron dispatcher is located outside the document root and hence not accessible by the public.
Posted Jan 1, 2007 by Matt Attlee
 

Comment

4 An alternative way of protecting your cron dispatch

Another way of securing the cron dispatcher that doesn't require a seperate file is to call php_sapi_name inside the index.php inside webroot - something like this :

if (isset($_GET['url']) && $_GET['url'] === 'favicon.ico') {
}
elseif(php_sapi_name() === "cli")
{
// this is a command line call - dispatch it with the argument
define('CRON_DISPATCHER',true);
$Dispatcher=new Dispatcher();
$Dispatcher->dispatch($argv[2]);
}
else {
$Dispatcher=new Dispatcher();
$Dispatcher->dispatch($url);
}
Posted Jan 9, 2007 by Mark Ng
 

Question

5 alternative way is giving error

@Mark

I tried using your alternative way. I just added the else if condition in my webroot/index.php and using command line , I tried calling "php index.php controller/actions/parameter" -- also index.php recognizes it as a command line call, I am getting an error that FULL_BASE_URL is not defined. Below is the error message I am getting

Notice (8): Use of undefined constant FULL_BASE_URL - assumed 'FULL_BASE_URL' [CORE\cake\libs\router.php, line 569]

Context | Code$url = "/users/logout/?from=/"
$full = true
$_this = Router object
$params = array("plugin" => null, "controller" => "articles", "action" => "indexer", "pass" => array, "form" => array, "url" => array, "bare" => 0, "webservices" => null)
$defaults = array("plugin" => null, "controller" => null, "action" => "index")
$path = array("plugin" => null, "controller" => null, "action" => null, "base" => false, "here" => "//articles/indexer/1", "webroot" => "/", "passedArgs" => array, "argSeparator" => ":", "namedArgs" => array, "webservices" => null)
$base = false
$frag = null
$q = null
$mapped = null
$output = "/users/logout/?from=/"
$extension = null



            $output = str_replace('//', '/', $output);


        }


        if ($full) {


            $output = FULL_BASE_URL . $output;


        }




Router::url() - CORE\cake\libs\router.php, line 569
Controller::redirect() - CORE\cake\libs\controller\controller.php, line 499
AppController::redirect() - CORE\app\app_controller.php, line 75
othAuthComponent::redirect() - CORE\app\controllers\components\oth_auth.php, line 804
othAuthComponent::check() - CORE\app\controllers\components\oth_auth.php, line 923
AppController::beforeFilter() - CORE\app\app_controller.php, line 62
Dispatcher::start() - CORE\cake\dispatcher.php, line 380
Dispatcher::_invoke() - CORE\cake\dispatcher.php, line 334
Dispatcher::dispatch() - CORE\cake\dispatcher.php, line 323
[main] - CORE\app\webroot\index.php, line 88



Posted Jul 14, 2007 by Ritesh Agrawal
 

Comment

6 Problem Solved for alternative way is giving error

the trick was to remove othAuth restriction on the controller function. Since the function won't be able through web anymore, the restriction didn't make much sense and was redirecting to a webpage.

Cheers :)
Ritesh
Posted Jul 15, 2007 by Ritesh Agrawal
 

Comment

7 Link to a working cron dispatcher file

The steps above are not cut and paste - in case anyone thinks they are. The link below has a cut and paste example, which worked first time for me.

http://www.mail-archive.com/cake-php@googlegroups.com/msg29333.html
Posted May 21, 2008 by JP Sykes
 

Comment

8 great

Works great for me in 1.2, just replace things below the include(...dispatcher.php) line. Didn't have to change paths using my production setup.

Thanks Matt!
Posted Jul 10, 2008 by Ben Snider
 

Comment

9 cant update

...just replace things below the include(...dispatcher.php) line....
Can't update my comment at the moment for some reason, but replace after include(...bootstrap.php) in 1.2.
Posted Jul 10, 2008 by Ben Snider