Catherine
Catherine

Reputation: 129

Cakephp cron job to call a controller's action

I started to use CakePHP (1.2) a few months ago to add small features to the company's application and I'm not too familiar with it.

We test locally then on a development server before merging to a production server.

I want a controller action to be called every hour with what I assumed to be the best way to do this through my researches, a cron job.


Attempt 1

After reading these,

http://bakery.cakephp.org/articles/mathew_attlee/2006/12/05/calling-controller-actions-from-cron-and-the-command-line

http://book.cakephp.org/1.2/en/view/110/Creating-Shells-Tasks

I could implement something without errors, but the action is not executed.

Based on these examples, I added a file named cron_dispatcher.php in my app directory (not app/webroot) and then did this command from the app dir

php cron_dispatcher.php /controller/action/param

Still nothing happened but it works perfect when I call it through the url.


Attempt 2

I tried creating a shell (email.php) that would call the action in /app/vendors/shells/.

<?php

class EmailShell extends Shell {

    public function main() {
        $this->out('Test');
    }

}
?> 

This successfully outputs Test in the console using

cake email main

but then I cannot find how to call the controller's action. I have tried

$this->requestAction('/controller/action');

I have also tried to make the call from a different function than the main in the shell.

I have tried to include the controller in the $uses variable as I would with a model but that didn't work (and it doesn't make sense I think)

I don't think creating a task is the solution either as I don't want to duplicate the sendEmails function hence why I'm looking for a way to just call the controller's action from a shell or whatever!

There is probably some theory I'm missing, thanks


Solution

I moved some methods from the controller to a model and I was able to call them from a shell.

App::import('Component', 'Email');

class SendMemosShell extends Shell {

    var $uses = array(
        'Memo',
    );

    public function main() {

    }

    public function sendEmails () {
        $this->Email =& new EmailComponent(null);
        $memoList = $this->Memo->getMemos();
        //...
    }
}

This link helped http://book.cakephp.org/2.0/en/console-and-shells/cron-jobs.html


edit : clarified some of the information and added the solution

Upvotes: 6

Views: 10259

Answers (2)

Luc Franken
Luc Franken

Reputation: 3014

It is a quite common issue actually, ran into it also.

A controller is deciding how to handle a request and starting that task. In this case there is no need for a controller since you have a shell task, the task is already clear.

Knowing that, it does not make sense to call a controller method.

So rethink your options, and yes this is a quite difficult one. For example you might decide that sending the e-mail is a business logic step so it should be in the model. Another option is to separate it totally (that's what we like most).

In that case you will have to create a queue where you put in all e-mails to send. That is a good design since you then know the amount of logic in the controller goes down and it is separated. That way you get an e-mail service.

For example you could ask the service to send a "new user" mail. Then you add the User object to it and it should handle itself. That way you can even scale since your service could be for example outsourced, you could expand multiple servers on the service etc.

Edit:

Good questions.

Steps to take:

  1. Centralize the "sending e-mail" process first. So choose one location where to put it. The you can decide: Add to send e-mail to a queue or call the service directly. For example you could add shell task for sending the e-mails.

  2. Call the shell: Now you have the problem to call the shell. In general you don't want to. Why not? Because a shell (a task) could run for a long time. So that's why we use queues in between. So you can ask the queue or let the queue message you that something is done. For example think about a mail server which is down. You have to retry etc. That should not be in a web request because the user is waiting for response.

  3. Third step is to call the shell from your cron, now that's easy since you are already on the command line so you could use standard calls.

Anyhow, there are options to do a direct call from a controller but you should not. This post gives some very interesting insights: CakePHP: Run shell job from controller

Edit 31/08/'13: See the events system of CakePHP also for some examples: http://book.cakephp.org/2.0/en/core-libraries/events.html

Upvotes: 3

ifunk
ifunk

Reputation: 637

Depending on what needs to be done, I often keep these methods in my controller actions. At the top of the action I check $_SERVER['REMOTE_ADDR'] == $_SERVER['SERVER_ADDR'] to ensure only the website can call the action. Then in cron I would curl or wget this address.

It has its benefits - easier to run locally during development (just enter the url in your browser), plus there are some differences between running cli version of php and apache version, as well as the request variables (eg. cake can't get the website domain/address through cli like you can running as apache module, so absolute links to the website using html helper don't work).

Upvotes: 0

Related Questions