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:-
Download code
Now replaces everything below it with the following code:-
Download 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
Download code
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
Comment
1 Protecting this from the public
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 "";
}
Bug
2 Didnt seem to work
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
Comment
3 Tutorial updated
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.
Comment
4 An alternative way of protecting your cron dispatch
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);
}
Question
5 alternative way is giving error
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
Comment
6 Problem Solved for alternative way is giving error
Cheers :)
Ritesh
Comment
7 Link to a working cron dispatcher file
http://www.mail-archive.com/cake-php@googlegroups.com/msg29333.html
Comment
8 great
Thanks Matt!
Comment
9 cant update
Can't update my comment at the moment for some reason, but replace after include(...bootstrap.php) in 1.2.
Comment
10 Please Help! I cannot get my cron to work.
/* 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.
Comment
11 Use a shell - it's built for cron jobs:
Comment
12 creating my own shell doesn't work either...
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.
Comment
13 Updated link
Comment
14 Cron Controller
// 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! :)
Comment
15 Fatal error: Can't find application core file
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