Calling controller actions from cron and the command line

by mathew_attlee
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:-


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
}

Report

More on Tutorials

Advertising

Comments

  • sharmil posted on 01/22/11 03:24:45 AM
    Hi,

    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
    • davidcroda posted on 06/24/11 06:44:44 PM
      [quote] Hi,

      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
  • nidhinbaby posted on 07/06/10 07:57:56 AM
    Hello

    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
  • Jijarine posted on 01/28/10 09:29:01 AM
    A more simplistic way of doing this, if your on a linux / unix machine:

    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
  • sk8ter posted on 01/28/10 05:21:34 AM
    ok ....it works now!!

    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?
  • rvdvin posted on 01/19/10 09:33:48 AM
    I succesfully implemented cakephp cron jobs using this tutorial. There is only one problem. The controller actions I call using a cron job run twice! I did some testing but can't find the source of the duplication.

    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!
  • ulifigueroa posted on 05/30/09 01:26:07 PM
    When I tried to run the cron dispatcher this way:


    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
    • stuchy posted on 12/09/09 08:40:32 AM
      When I tried to run the cron dispatcher this way:


      php cron_dispatcher.php /controller/action

      I get the following error
      [...]
      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:

          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__))));
          }
      into this:


          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 .."
  • mikesmullin posted on 05/09/09 09:27:09 PM
    Instead of this in every controller method:


    // 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! :)
  • FrankTheTank posted on 03/02/09 10:13:59 AM
  • michaelc posted on 10/28/08 09:55:45 AM
    Please read http://book.cakephp.org/complete/108/The-CakePHP-Console for instructions on how to create a cron job in 1.2 without even touching the core files :).

    (edited 18 Sept 2009 to update the link)
    • charlotteg22 posted on 10/28/08 02:08:58 PM
      Please read http://manual.cakephp.org/complete/108/The-CakePHP-Console for instructions on how to create a cron job in 1.2 without even touching the core files :).
      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.

      • michaelc posted on 09/18/09 04:11:15 PM
        In response to #12 (too long to quote, but the information is relevant): You need to pass the -app option with the full path to your app directory immediately following. You typically also need this option passed inside of your crontab file, so beware.

        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.
  • charlotteg22 posted on 10/27/08 06:52:53 PM
    The path of my cron_dispatcher.php is in: /var/www/vhosts/ninthlink.com/httpdocs/pdf/pdfadmin/webroot/cron_dispatcher.php that looks like:
    /* 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.
  • stupergenius posted on 07/10/08 09:13:45 AM
    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!
    • stupergenius posted on 07/10/08 09:22:07 AM
      ...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.
  • jpsykes posted on 05/21/08 08:21:25 PM
    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
  • markng posted on 01/09/07 03:57:27 AM
    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);
    }
    • ragrawal posted on 07/14/07 02:58:25 PM
      @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



      • ragrawal posted on 07/15/07 12:31:55 AM
        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
  • Dieter_be posted on 12/06/06 08:25:42 AM
    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 "";
    }
    • mathew_attlee posted on 01/01/07 01:38:05 AM
      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.
    • stabbie posted on 12/19/06 06:07:06 AM
      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
login to post a comment.