Use cases

Sending emails

Sending emails is probably the most common use case of background jobs. Everyone need to send emails.

The problem

Sending emails can be slow. Depending on what type of emails you're sending (sign in confirmation, notification, etc ...), you'll probably need to fetch additional datas from the database for the content. You'll also probably need to parse and render a template to format the email.

This is what you probably have

# User model class
App::uses('CakeEmail', 'Network/Email');
class User extends AppModel
{
    public function signUp($data) {
        if ($this->save($data)) {
            # Send email
            # See http://book.cakephp.org/2.0/en/core-utility-libraries/email.html for details
            $email = new CakeEmail();
            $email->template('welcome', 'fancy')
                ->viewVars($this->datas)
                ->emailFormat('html')
                ->to($this->data['User']['email'])
                ->from('app@domain.com')
                ->send();
            return true;
        }
        return false;
    }
}

Firstly, there's something wrong with this application design. User class should not be coupled the CakeEmail class.
Secondly, the email sending is slowing down the workflow.

The application design part is easily solved by using Events. But even if the email sending is moved to another part of the application, it's still inside the main workflow of signing up a user. It's CakeResque time.

The solution

This is one solution among others.

Step 1 : Move all the non-User class related stuff to an event

# User model class
class User extends AppModel
{
    public function signUp() {
        if ($this->save($this->data)) {
            $this->getEventManager()->dispatch(new CakeEvent('Model.Order.afterSignUp', $this));
            return true;
        }
        return false;
    }
}

Step 2 : Enqueue the job in the event callback

Let's register the callback first. In this particular example, we'll register it in the UsersController.
You can register it elsewhere, depending on your application design.

class UsersController extends AppController
{
    public function signUp() {
        $this->User->getEventManager()->attach(
            function($event) {
                // In this case, $event->subject() == $this->User
                CakeResque::enqueue('email', 'EmailSenderShell', array('sendSignUpEmail', $event->subject()->id))
            },
            'Model.User.afterSignUp'
        );

        if ($this->User->signUp($this->data)) {
            ...
        }
    }
}

Step 3 : Create the job class

# app/Console/Command/EmailSenderShell.php
App::uses('CakeEmail', 'Network/Email');
class EmailSenderShell extends AppShell
{
    public function sendSignUpEmail() {
        // We don't have access to User object anymore
        // We need to create it, and re-fetch all the pertinent datas
        $user = ClassRegistry::init('User')->find('first', array('conditions' => array('id' => $this->args[0])));

        // $this->args[0] == the User id, passed when queuing the job with CakeResque::enqueue() at step 2

        $email = new CakeEmail();
        $email->template('welcome', 'fancy')
            ->viewVars($user)
            ->emailFormat('html')
            ->to($user['User']['email'])
            ->from('app@domain.com')
            ->send();
    }
}

Bonus 1: Send email via the Cake Console

cake emailSender sendSignUpEmail USER-ID

Bonus 2: Re-enqueue the email if sending failed

Sometimes, sending an email can fail. This can happen when you're using an external service (like gmail) that's down, or taking too much time to respond, to send your emails.
You can try to resend it, by queuing it again.

# app/Console/Command/EmailSenderShell.php
App::uses('CakeEmail', 'Network/Email');
class EmailSenderShell extends AppShell
{
    public function sendSignUpEmail() {
        // We don't have access to User object anymore
        // We need to create it, and re-fecth all the pertinent datas
        $user = ClassRegistry::init('User')->find('first', array('conditions' => array('id' => $this->args[0])));

        // $this->args[0] == the User id, passed when queuing the job with CakeResque::enqueue() at step 2

        $email = new CakeEmail();
        try {
            $email->template('welcome', 'fancy')
                ->viewVars($user)
                ->emailFormat('html')
                ->to($user['User']['email'])
                ->from('app@domain.com')
                ->send();
        } catch (SocketException $e) {
            // Resend the email if fail
            // We're assuming here that the only possible SocketException is the one thrown
            // when the email fail to be sent
            CakeResque::enqueue('email', 'EmailSenderShell', array('sendSignUpEmail', $this->args[0]));
        }
    }
}

Resizing image/Creating thumbnails

Coming soon ...

Cache warming up

Coming soon ...