tags:

views:

5040

answers:

9

I have a very simple model. I want to add a send email routine to on of the methods for the model:

$this->Email->delivery = 'smtp';
$this->Email->template = 'default';
$this->Email->sendAs = 'text';     
$this->Email->from    = 'email';
$this->Email->to      = 'email';
$this->Email->subject = 'Error';

I've tried putting

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

at the top, to no avail. The error I get is:

Fatal error: Call to undefined method stdClass::send() in E:\xampp\htdocs8080\app\models\debug.php on line 23

Any ideas?

I'm running CakePHP 1.2

+1  A: 

Components are supposed to be used in controllers, not models.

In your controller use

var $components = array('Email');

There is no need to use App::import();

Without knowing your app and reasons for wanting to use it in a model, I might suggest you re-think your system architecture and move this logic to your controller.

If you definitely need it in your mode, your code included something like...

$this->Email->delivery = ...

Have you put created a new instance of the component and set it to a property of your model called Email? (No idea if this will work mind.)

The error you are getting is because you are calling the send() method on a the stdClass object i.e. not an instance of the EmailComponent.

neilcrookes
+1  A: 

OK, true it isn't good to use components in models. My problem was I didn't want to have to write the email block a million times in my controllers:

$this->Email->delivery = 'smtp';

$this->Email->template = $template;

$this->Email->sendAs = 'text';

$this->Email->from = $from;

$this->Email->to = $to;

$this->Email->subject = $subject;

$this->Email->send();

Hardly DRY if I use this 5 times in a controller. So I created a component called Wrapper, and added a sendEmail routine, so I can just write:

$this->Wrapper->sendEmail($from,$to,$subject,$template,$body);

Justin
+5  A: 

Hello,

even if it is not best practice, you actually can use the EmailComponent in a model, but you need to instanciate it (in the Models there is no automatic Component loading) and you need to pass it a controller. The EmailComponent relies on the Controller because of the connection it needs to the view, for rendering email templates and layouts.

With a method like this in your model

function sendEmail(&$controller) {
    App::import('Component', 'Email');
    $email = new EmailComponent();
    $email->startup($controller);
}

You can use it in your Controller like this:

$this->Model->sendEmail($this);

(omit the & in the method signature if you're on PHP5)

rscherer
+8  A: 

Well, you're doing it wrong way. You should place email send routine in your AppController:

function _sendMail($to,$subject,$template) {
    $this->Email->to = $to;
    // $this->Email->bcc = array('[email protected]'); // copies
    $this->Email->subject = $subject;
    $this->Email->replyTo = '[email protected]';
    $this->Email->from = 'MyName <[email protected]>';
    $this->Email->template = $template;
    $this->Email->sendAs = 'text'; //Send as 'html', 'text' or 'both' (default is 'text')
    $this->Email->send();
}

Then use it from ANY controller like this:

$this->_sendMail($this->data['User']['email'],'Thanks for registering!','register');

And don't forget to place

var $components = array('Email');

in controllers, in which you 're using _sendMail function.

Sergei
Thanks! "Please enter at least 10 characters." is a dumb requirement.
Justin
Really?
spoulson
Yes, I can't just say thanks. Annoying.
Justin
can i add this to app model instead ?
Harsha M V
+1  A: 

I'm with you Justin.

I have a series of Models that trigger emails based on certain actions that can come from Users, Admins and shell scripts for automated processes.

It is FAR easier to centralize an email response in the Model (like when an Order record is 'cancelled') than to rewrite the email in multiple locations.

Also, I have automated processes in the Models that handle some core 'logic' that cascade to other hasOne, belongsTo or hasMany Models that are biz rules.

For example, a crontabbed shell script calls Offer->expire() to 'expire' an Offer which then calls Offer->make() to make another Offer, but if it can't then it calls Request->expire() to 'expire' the original request. Emails must be sent to first expired Offer recipient, to any new Offer recipients and/or to the requestor if it expires. These can be called by crontabbed shell or by Users or by Admins who can manage Requests and Offers manually. All using different Controllers or interfaces.

This is what I did and can call it inside Models and Controllers:

if(empty($this->MailsController)) {
    App::import('Controller','Mails');
    $this->MailsController = new MailsController();
    $this->MailsController->constructClasses();
    $this->MailsController->Email->startup($this->MailsController);
}

Now I am able to call this from just about anywhere and centralize all the logic for what data to find(), what email to generate, whom to send it to, etc. via the following called inside the Model:

$this->MailsController->orderMail($user_id,$this->id,$mode);

Since all of the email logic is basically called by the Models indirectly via MailsController, I am going to give rscherer's code a try.

Hope this helps, oh4real

"You're not supposed to use the EmailComponent in a model. Use a controller" is really missing the point and based on arbitrary reasoning. In Rails emails are often sent from an Observer, which is basically a model Behavior in Cake, because emails usually happen on state changes.
method
A: 

I have an app where I add a single row to a model (model1). That model then triggers entries into another model (model2). model1 hasmany model2. I wanted to only send emails if model2 inserts successfully. So I was looking to have model1 send emails each time it inserts successfully into model2.

Restructuring the system architecture is too much time at this point.

My solution? When I do a Model1->addRecord, it does a model2->addRecord(). I have model1 keep track of each failure or success from model2->addRecord. From model1->addRecord I then return an array of success/failure to the invoking controller (model1_controller). From there I will then have model1_controller issue the emails.

So it is possible to do this correctly without having to completely rewrite the architecture. Just return more information from Model1 to the controller, then have the controller (properly) send the emails.

Sorry for the somewhat unclear post. My brain is in codemode right now.

Travis Leleu
A: 

Look into a plugin called Eventful

It will allow you to broadcast and listen for custom events. For the poster below me : you could broadcast a Model1Saved event, listen for it in model2, then perform the second save and them broadcast a success event back to model1, or even a controller. From there you can send your email.

Abba Bryant
A: 

I've used App::import('Component', 'Email'); $email = new EmailComponent(); $email->startup($controller);

and it worked. but i 'm not able to set variables to templates like we simple do in controllers for email : $this->set('siteinvitees', $siteinvite);
Please guide me for the same.

dipali