« back

CakePHP: Running shells via CronJobs

Posted Dec 14th 2009, 14:03 by PaulGardner

Are you a developer using CakePHP based in the North East of England? If so, get in touch if you would be interested in being part of a group of developers who meet up once or twice a month to bounce ideas off one another.

The Console

The CakePHP Console allows you to access your MVC classes via a cron job or other command-line script.  If you have baked your models/controllers/views you have already used it in conjunction with the bake library kindly provided by the Cake Dev Team.

But did you know this is also the most secure way to periodically run scripts in your application? 

When you run the following from your command line

$ cd /my/cake/app_folder
$ ../cake/console/cake

you will see something like

Hello user,
 
Welcome to CakePHP v1.2 Console
---------------------------------------------------------------
Current Paths:
 -working: /path/to/cake/
 -root: /path/to/cake/
 -app: /path/to/cake/app/
 -core: /path/to/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:
 
 app/vendors/shells/:
         - none
 
 vendors/shells/:
         - none
 
 cake/console/libs/:
         acl
         api
         bake
         console
         extract
 
To run a command, type 'cake shell_name [args]'
To get help on a specific command, type 'cake shell_name help'

The last list of items shows the libraries shipped with Cake core.  This tutorial describes how to add a shell to the 'app/vendors/shells' list and call it from a cron job.

The Shell

In /app/vendors/shells create a file named alerts.php with the following code:

<?php
class AlertsShell extends Shell {

  function main() {
    ClassRegistry::init('Comment')->reminders();
  }

}
?>

All this file does is calls the reminders() function from a Comment model.  I am using the ClassRegistry::init() function here as I think it's cleaner (only calls the model when you need it and will only instantiate it once), but you could specify var $uses = array('Comment'); if you wanted.

The Model

In our model we now need to create the reminders() function:

function reminders() {
  // approved and unedited
  $data = $this->find('all', array(
    'conditions' => array(
      'Comment.status' => 'approved',
      'Comment.created = Comment.modified',
      'DATE_ADD(Comment.created, INTERVAL 3 HOUR) >=' => date('Y-m-d')
    )
  ));
  if(!empty($data)) {
    foreach($data AS $row):
      $approvedComments[] = "<a href='/admin/comments/index/".$row['BlogPost']['id']."#comment".$row['Comment']['id']."'>".$row['BlogPost']['title']." #".$row['Comment']['id']."</a>";
    endforeach;
    $saveData['Message'][] = array(
      'subject' => 'New auto-approved comments',
      'body' => '<p>Just a quick note to remind you that some auto-approved comments were added recently which if you have not already reviewed you should do so now.</p>
      <ul><li>'.join('</li><li>', $approvedComments).'</li></ul>'
    );
  }

  // pending
  $data = $this->find('all', array(
    'conditions' => array(
      'Comment.status' => 'pending'
    )
  ));
  if(!empty($data)) {
    foreach($data AS $row):
      $pendingComments[] = "<a href='/admin/comments/index/".$row['BlogPost']['id']."#comment".$row['Comment']['id']."'>".$row['BlogPost']['title']." #".$row['Comment']['id']."</a>";
    endforeach;
    $saveData['Message'][] = array(
      'subject' => 'Comments still pending',
      'body' => '<p>Just a quick note to remind you that you have some pending comments which you should review and approve or mark as spam.</p>
      <ul><li>'.join('</li><li>', $pendingComments).'</li></ul>'
    );
  }

  ClassRegistry::init('Message')->saveAll($saveData['Message'], array('atomic'=>false));
}

This is a function I am using to provide alert messages to a site owner who is using MilesJ's Commentia Behaviour.  When executed it:

  1. Checks for automatically approved comments which have not been edited
  2. Checks for any comments with a pending status

Once it has ran these checks and added any messages into a $saveData array the last line then uses ClassRegistry::init() to call Message->saveAll().

The Console Revisited

To test the above was working I then went back to me command-line and ran

/home/serverUser/domains/domain.org.uk/public_html/cake/console/cake -app "/home/serverUser/domains/domain.org.uk/public_html/app" alerts

You would have to alter the paths to match your server setup and where your app is located, but the above is what the cron job will run and if successful you should simply see:

Welcome to CakePHP v1.2.4.8284 Console
---------------------------------------------------------------
App : app
Path: /home/serverUser/domains/domain.org.uk/public_html/app
---------------------------------------------------------------

 

If there are any errors encoutered whilst running your shell they will be displayed on screen for you to deal with, something that is very difficult to see if you jump stright to running this from a cron job, so this step is worthwhile.

The Cron Job

The next task is to create a cron job, now I have Direct Admin on our server so this is an easy task, but whatever system you have access to you need to use it to create a cronjob similar too:

 

* */3 * * * /home/serverUser/domains/domain.org.uk/public_html/cake/console/cake -app "/home/serverUser/domains/domain.org.uk/public_html/app" alerts

This is nearly identical to the command I ran from the command-line to test my shell, but has 5 parameters at the start to set your minutes, hours, day of month, month, and day of week settings.  I have the second parameter set to */3 to say I want my cron job to run every 3 hours.

Finishing Off

And finally there are a few extra things which need attention before this would work for me.

  1. Change line #30 of /cake/console/cake file to include the full path to the php cli command
    30: exec /full_path/php -q ${LIB}cake.php -working "${APP}" "$@"
  2. Change the permissions of /cake/console/cake so it can be executed (is used 754)

And that should hopefully be it .. any questions? leave a comment below.

Tags: tutorial cakephp shell cronjob

8 Comments

  1. Feb 3rd 2010, 18:13 by Akif

    Nice article, i didn't work untill i added the full path to the cake file as explained. For me it was: /usr/local/bin/php

    Thanks for the article!

  2. Feb 3rd 2010, 18:25 by PaulGardner

    @Akif: Glad you could make sense of the article. I am new to writing tutorials so it's great to know that someone has found this useful.

  3. Feb 3rd 2010, 20:09 by Akif

    Yeah keep up to good work, you will get more appriciation over time :)

    I have a question: is it possible to call an action from a specific controller from the shell? And if yes, would this break any rules/conventions? I have a sychronise action which is made and works, i would like to execute this in the cronjob...

    PS: whats your Twitter? Mine @aquive

  4. Feb 3rd 2010, 20:49 by PaulGardner

    @akif: I have not found a way to call specific controller actions in this manner, but please do not take this is a definitive indication that it cannot be done as I am by no means a CakePHP expert.

    However, I would imagine this would break the MVC design pattern as controllers exist to pull data from your models using your business logic and then process that data to be passed to a view. As such I am unsure if it would be correct to call a controller action from a shell.

    What is it your trying to achieve that has led you to wanting to call a controller atcion from a shell?

    P.S. the fact you are now following me on twitter means you found the twitter link in my 'keep in touch' section, have followed you and made you the first person listed in my cakephp-lovers list (http://twitter.com/WebbedIT/cakephp-lovers)

  5. Nov 26th 2010, 20:29 by mauro

    I have a trouble trying to send an email with cake shell and is driving me crazy. I need to use absolute urls inside the email, to do this I used $this->url(array(...), true) in the .ctp file to made this work, but this is not working properly. I readed something in an article in the bakery (http://bakery.cakephp.org/articles/Jippi/2007/12/02/emailcomponent-in-a-cake-shell) said that you need to include some things to make things work properly, then I added to my shell function these lines:

    App::import('Core', array('Router', 'Controller', 'Helper'));
    include CONFIGS . 'routes.php';
    define(FULL_BASE_URL, 'http://url/to/my/server');

    Whithout this I got an error saying that Router is not definied, but although I don't get the error anymore this is still not working properly because the final urls made by $this->url aren't right, now this make the url using the FULL_BASE_URL value concatenated to the direction that I put in the $this->url params, the same thing happen with the images.

    Do you know how to solve this?

    Thanks,
    Mauro.

  6. Dec 17th 2010, 18:15 by Jon

    Instead of changing the cake file, why not just set the PATH variable when defining the cronjob.

    Ex.
    PATH=/paths:/to_all:/the_scripts_i_need


    * * * * * cake -app /my_app_path my_shell

  7. Jan 9th 2011, 11:15 by PaulGardner

    @Mauro & Jon: Sorry it's taken so long to spot that both of your completely valid comments were blocked by my auto-anti-spam system :(

    @Mauro: Off the top of my head I do not know the answer to your problem. If you're ever stuck in CakePHP, I suggest you first search for an answer in the excellent Google Group (http://groups.google.com/group/cake-php) and if you cannot find a related thread, post your own. There are hundreds of regulars keeping an eye on that group so you're much more likely to get a quicker response there than from a blog such as mine.

    @Jon: I have not tried this, but it is certainly another option that I am now aware of, thanks :)

  8. Feb 9th 2011, 09:01 by Joffz

    Sir,
    I followed your article and was successful to execute cron shell in cake php through terminal.Example : /full path to/cake/console/cake -app "/full path to/app" scheduled . The shell was to send sms which are scheduled on a particular date. I am using ubuntu and is normal user like no permission for accessing root files. The problem is i can execute the cron shell through terminal by typing as in example given above but, it is not working while setting crontab. Any help will be highly appreciated.

Leave a comment

Why not leave a comment for the author and others to read?

Get In Touch

Keep In Touch

Twitter Updates

    follow us on Twitter