developing and hosting web sites and applications for the Third Sector and SMEs since 2003
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 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.
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.
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:
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().
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 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.
And finally there are a few extra things which need attention before this would work for me.
And that should hopefully be it .. any questions? leave a comment below.
4 Comments
Feb 3rd, 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!
Feb 3rd, 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.
Feb 3rd, 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
Feb 3rd, 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)
Leave a comment
Why not leave a comment for the author and others to read?