Calling controller actions from cron and the command line
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:-
Now replaces everything below it with the following code:-
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
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:-
require CORE_PATH.'cake'.DS.'bootstrap.php';
Now replaces everything below it with the following 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
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
}

Has anyone else run in to the need to pass query string parameters along with /controller/action (i.e. /controller/action?x=1&y=2 ...)?
First issue is that when I try running the following command:
php cron_dispatcher.php /mail/send?city=abc&user_type=2, the command hangs. It seems that '&' is the issue but I can't figure out why.
Second issue is that print_r($this->params['url']) does not provide me 'city' and 'user_type' as a key=>value pair.
Any help would be greatly appreciated.
Regards,
Sharmil
Has anyone else run in to the need to pass query string parameters along with /controller/action (i.e. /controller/action?x=1&y=2 ...)?
First issue is that when I try running the following command:
php cron_dispatcher.php /mail/send?city=abc&user_type=2, the command hangs. It seems that '&' is the issue but I can't figure out why.
Second issue is that print_r($this->params['url']) does not provide me 'city' and 'user_type' as a key=>value pair.
Any help would be greatly appreciated.
Regards,
Sharmil
[end quote]
You need to wrap the query string in quotes. The ampersand has its own special meaning on the command line
I have created cron_dispatcher.php and placed it in the app folder.
I have return some test email function for the cron job to run in my users controller's test method.
And i have created a Cron job in my web server's control panel like "/usr/bin/php /home4/enventur/public_html/pennystock/cron_dispatcher.php /users/test"
But its giving me an error as "No input file specified."
can you please help me, how to solve it
Thanks in Advance
lynx --dump http://www.domain.com/controller/action/ >/dev/null
So for a cron job:
*/5 * * * * lynx --dump http://www.domain.com/controller/action/ > /dev/null
I tried in my local machine
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
$this->log('cron works!!!!!!!!!');
}
i'm getting succefully message!!
but,.....uh....how to make this process automated everytime ...
like, make it work everyday at midnight?
When I call the action directly through a webbrowser (http://www.domain.com/controller/action) it is only performed once.
Even when I call the action directly through a webbrowser and through cron_dispatcher.php (http://www.domain.com/cron_dispatcher.php?url=/controller/action) it is performed only once. (I know having the cron_dispatcher.php in /app/webroot/ is a security risk. This is only a test setup.)
And when I call a different php file (not part of a cakephp application) using a cron job, it is only executed once.
So both cron and cron_dispatcher.php are no source of duplication on their own. There be something in the combination of the two that causes the controller action to run twice when using a cron job.
I hope someone can help me out here. Maybe someone experienced the same problem and found a solution for it.
Thanks in advance!
php cron_dispatcher.php /controller/action
I get the following error
Warning: include(/home/user/public_html/website/config/core.php): failed to open stream: No such file or directory in /home/user/public_html/website/cake/libs/configure.php on line 648
Warning: include(): Failed opening '/home/user/public_html/website/config/core.php' for inclusion (include_path='/home/user/public_html:/home/user/public_html/website/:.:/usr/lib/php:/usr/local/lib/php') in /home/user/public_html/website/cake/libs/configure.php on line 648
Fatal error: Can't find application core file. Please create /home/user/public_html/website/config/core.php, and make sure it is readable by PHP. in /home/user/public_html/website/cake/libs/configure.php on line 649
What other variable do I have to change?
My Cake Version is: 1.2.1.8004
The problem is, that the index.php was in the webroot directory and your new dispatcher works in the app-dir. Try to change this in your dispatcher:
into this:if (!defined('ROOT')) {
define('ROOT', dirname(dirname(dirname(__FILE__))));
}
/**
* The actual directory name for the "app".
*
*/
if (!defined('APP_DIR')) {
define('APP_DIR', basename(dirname(dirname(__FILE__))));
}
if (!defined('ROOT')) {
define('ROOT', dirname(dirname(__FILE__)));
}
/**
* The actual directory name for the "app".
*
*/
if (!defined('APP_DIR')) {
define('APP_DIR', basename(dirname(__FILE__)));
}
Calling nested "dirname" has the same effect like a "cd .."
// Check the action is being invoked by the cron dispatcher
if (!defined('CRON_DISPATCHER')) {
$this->redirect('/');
exit();
}
I prefer this:
Cron::dispatch_required(); // disallow browser requests
To do this I just created a Cron component:
<?php
class CronComponent extends Object {
/**
* Verify CakePHP was invoked by the cron dispatcher, or die.
*/
function dispatch_required() {
if (!defined('CRON_DISPATCHER')) {
$this->redirect('/');
exit();
}
}
}
Hope this helps someone! :)
(edited 18 Sept 2009 to update the link)
I followed the exact steps in that tutorial; I created a simple report.php inside my /vendors/shells directory that looks like this:
class ReportShell extends Shell {
function main()
{
$this->out('hello');
}
}
?>
So when I run this report by executing this command:
[root@www shells]# cake report
I get the default output:
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
Current Paths:
-app: shells
-working: /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin/vendors/shells
-root: /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin/vendors
-core: /usr/lib/cake/
Changing Paths:
your working path should be the same as your application path
to change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp
Available Shells:
cake/console/libs/:
testsuite
api
acl
bake
schema
console
i18n
To run a command, type 'cake shell_name [args]'
To get help on a specific command, type 'cake shell_name help'
What am I doing wrong? Thanks in advance.
To those still trying to use this method: cron_dispatcher.php is a huge security risk. Anybody can run your cron jobs over the web. Often, if you have things that you want shared between the controller and the shell, they should be placed in the relevant model, so that either can call them. Using shells is as secure as a real cron job, and permits using most of the Cake framework even still. I give my strongest recommendation that you learn to use shells instead.
/* SVN FILE: $Id: index.php 7296 2008-06-27 09:09:03Z gwoo $ */
/**
* Short description for file.
*
* Long description for file
*
* PHP versions 4 and 5
*
* CakePHP(tm) : Rapid Development Framework
* Copyright 2005-2008, Cake Software Foundation, Inc.
* 1785 E. Sahara Avenue, Suite 490-204
* Las Vegas, Nevada 89104
*
* Licensed under The MIT License
* Redistributions of files must retain the above copyright notice.
*
* @filesource
* @copyright Copyright 2005-2008, Cake Software Foundation, Inc.
* @link http://www.cakefoundation.org/projects/info/cakephp CakePHP(tm) Project
* @package cake
* @subpackage cake.app.webroot
* @since CakePHP(tm) v 0.2.9
* @version $Revision: 7296 $
* @modifiedby $LastChangedBy: gwoo $
* @lastmodified $Date: 2008-06-27 02:09:03 -0700 (Fri, 27 Jun 2008) $
* @license http://www.opensource.org/licenses/mit-license.php The MIT License
*/
/**
* Use the DS to separate the directories in other defines
*/
if (!defined(’DS’)) {
define(’DS’, DIRECTORY_SEPARATOR);
}
/**
* These defines should only be edited if you have cake installed in
* a directory layout other than the way it is distributed.
* When using custom settings be sure to use the DS and do not add a trailing DS.
*/
/**
* The full path to the directory which holds “appâ€, WITHOUT a trailing DS.
*
*/
if (!defined(’ROOT’)) {
define(’ROOT’, dirname(dirname(dirname(__FILE__))));
}
/**
* The actual directory name for the “appâ€.
*
*/
if (!defined(’APP_DIR’)) {
define(’APP_DIR’, basename(dirname(dirname(__FILE__))));
}
/**
* The absolute path to the “cake†directory, WITHOUT a trailing DS.
*
*/
if (!defined(’CAKE_CORE_INCLUDE_PATH’)) {
define(’CAKE_CORE_INCLUDE_PATH’, ‘/usr/lib/cake’);
}
/**
* Editing below this line should not be necessary.
* Change at your own risk.
*
*/
if (!defined(’WEBROOT_DIR’)) {
define(’WEBROOT_DIR’, basename(dirname(__FILE__)));
}
if (!defined(’WWW_ROOT’)) {
define(’WWW_ROOT’, dirname(__FILE__) . DS);
}
if (!defined(’CORE_PATH’)) {
if (function_exists(’ini_set’) && ini_set(’include_path’, CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get(’include_path’))) {
define(’APP_PATH’, null);
define(’CORE_PATH’, null);
} else {
define(’APP_PATH’, ROOT . DS . APP_DIR . DS);
define(’CORE_PATH’, CAKE_CORE_INCLUDE_PATH . DS);
}
}
if (!include(CORE_PATH . ‘cake’ . DS . ‘bootstrap.php’)) {
trigger_error(â€Can’t find CakePHP core. Check the value of CAKE_CORE_INCLUDE_PATH in app/webroot/index.php. It should point to the directory containing your †. DS . “cake core directory and your †. DS . “vendors root directory.â€, E_USER_ERROR);
} else {
define(‘CRON_DISPATCHER’, true);
if($argc == 2) {
$Dispatcher = new Dispatcher();
$Dispatcher -> dispatch($argv[1]);
}
}
if (isset($_GET['url']) && $_GET['url'] === ‘favicon.ico’) {
return;
} else {
$Dispatcher = new Dispatcher();
$Dispatcher->dispatch($url);
}
if (Configure::read() > 0) {
echo ““;
}
?>
I am in the folder: /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin and on the command line I run:
php -q /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin/webroot/cron_dispatcher.php /pdfs/hello
where my controller is pdfs_controller.php and the function I want to call is:
function hello()
{
die(’hello’);
}
But the output is empty:
[root@www pdfadmin]# php -q /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin/webroot/cron_dispatcher.php /Pdfs/hello
[root@www pdfadmin]#
Please help!! Don’t know what I’m doing wrong. I’ve been looking everywhere but I can’t find out what the problem is. Thank you in advance.
Thanks Matt!
Can't update my comment at the moment for some reason, but replace after include(...bootstrap.php) in 1.2.
http://www.mail-archive.com/cake-php@googlegroups.com/msg29333.html
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);
}
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
Cheers :)
Ritesh
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 "";
}
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.
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