CrugeMailer

De Yii Framework en Español (Yiiñ)

Por:


Contenido

CRUGEMAILER

CrugeMailer es un -componente de negocio- para gestionar los correos basados en vistas para tu aplicación web. No solo esta hecho para Cruge, sino para proveer una plataforma de email basada en vistas para TODA TU APLICACION.

  • Permite que tu aplicación envie correos basados en vistas.
  • Centraliza el método de envío de email.
  • Convierte al correo en un componente de negocio.
  • Permite crear Cronjobs o envios programados en forma de lotes. (ver tema al final).

Obtener CrugeMailer

Si tu aplicación web esta hecha con Cruge no debes preocuparte de instalarlo porque ya Cruge lo trae. De todos modos si quieres instalar CrugeMailer en forma aislada puedes ir a este Repositorio de CrugeMailer.

CASO REAL: Crea tu propia clase MiCrugeMailer

Aqui tienes un caso real de uso de CrugeMailer. Ir al enlace

Información para Nerds

CrugeMailer no es "una libreria". La razón es simple, una librería se limita a proveer funciones encapsuladas listas para ser usadas en cualquier parte. CrugeMailer es un -componente de negocio-, podría decir con certeza y basado en conocimiento serio de OOP que CrugeMailer implementa para tu aplicación un "Polimorfismo de Negocio", el cual significa que (análogo al concepto de polimorfismo de oop) el comportamiento de CrugeMailer cambia de acuerdo a la llamada de negocio que se le pide: CrugeMailer no esta hecho para pedirle: "crugemailer.sendemail"..NO. CrugeMailer esta hecho para pedirle cosas como:

 "crugemailer.reportedeinventario($usuario, 'aqui va el informe del mes');"

Primero es necesario que comprendas que no existe un método público de crugemailer al cual llamar para enviar un correo, me refiero a que -no verás- llamadas como esta:

 Yii::app()->crugemailer->sendEmail("hola","alguien@gmail.com","body");  // asi no se usa CrugeMailer

En cambio si verás llamadas muy específicas que tu mismo creas en una clase derivada de CrugeMailer, por ejemplo:

 Yii::app()->crugemailer->dimeHola("hola",$alguien);    // esta si es la manera de usar CrugeMailer

Lo que se haces es que internamente dentro de esa función de ejemplo 'dimeHola' se invocará a una vista que recibirá como argumento el texto "hola" (usando las funciones que CrugeMailer provee), se imprimirá el contenido del mensaje sobre una vista para posteriormente enviarsela por correo a $alguien.

Podrías preguntarte ¿ Y cómo selecciono que método será usado para enviar el correo ? mail() ? phpmailer() ? otro() ?

Es decisión tuya: en esa clase derivada de CrugeMailer que tu debes crear, sobreescribirás el método sendEmail para especificar (o implementar) el método de tu preferencia.

Siguiendo el ejemplo de arriba, es dentro del método 'dimeHola' de tu clase extendida de CrugeMailer donde se especifica cómo se enviará ese correo, si por mail() o por otro método de tu preferencia.

Comprende que a la aplicación entera no le compete este detalle de cómo se envía el email, a la aplicación le interesa "decirle hola a alguien". Este es concepto tras CrugeMailer, y es lo que Cruge usa. Esto es el concepto de Encapsulamiento de la OOP hecho práctica.

Ejemplo de uso de CrugeMailer

 Yii::app()->crugemailer->enviarEmailNuevaClave($usuario); 

(es un ejemplo, la función 'enviarEmailNuevaClave' no existe en Cruge, pero si verás otras similares)


Configurar CrugeMailer

 
  'crugemailer'=>array(
  'class' => 'application.modules.cruge.components.CrugeMailer',
  'mailfrom' => 'email-desde-donde-quieres-enviar-los-mensajes@xxxx.com',
  'subjectprefix' => 'Tu Encabezado del asunto - ',
  'debug' => true,
),
  

El argumento class es quien cuál es la clase que implementará al componente crugemailer, por tanto si creas una nueva clase derivada de CrugeMailer (que extiende de..), deberás poner ahi la nueva ruta y toda la aplicación web incluso cruge se aprovecharán de este cambio.

Si en esta clase derivada sobreescribes el metodo sendMail verás que toda la aplicación enviará correos mediante ese metodo que tu seleccionas.

Personalizar CrugeMailer

Hay dos escenarios: Con Cruge y Sin Cruge. La diferencia ? Si tu aplicación no usa Cruge entonces derivas tu clase extendida de CrugeMailerBase, si usas Cruge debes extenderla de CrugeMailer para que Cruge no cause errores, esto es porque Cruge y tu aplicación compartiran el mismo componente. Si tu caso es "Con Cruge" será mejor que vayas a este enlace aunque el ejemplo del enlace puede perfectamente aplicarse para todo salvo la diferencia de usar CrugeMailer como clase base en vez de CrugeMailerBase.

Supongo que quieres crear un nuevo tipo de correo basado en una vista para tu aplicación, digamos que quieres algo como:

 Yii::app()->crugemailer->notificarStockMercancia($usuarioSeleccionado,"stock va a vencer producto xxx");

1. Creas una clase que extienda de "CrugeMailerBase", la llamarias por ejemplo: "MiClaseCrugeMailer" y supon que la guardas en tu directorio de 'protected/components/MiClaseCrugeMailer.php' (!!no dentro de cruge module!!)

 

<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function sendEmail($to,$subject,$body){
     // usa esto para que el correo se envíe por la via estandar mail()
     // si quieres usar otro método deberás comentar esta linea e implementar 
     // tu propio mecanismo.
     parent::sendEmail($to,$subject,$body);
  }
}

2. En el "config/main" del componente 'crugemailer' pones el argumento:

 'class' => 'application.components.MiClaseCrugeMailer'

3. Esta nueva clase contendría un nuevo método llamado "notificarStockMercancia": Notarás que uso una llamada a $this->render, es éste método el que renderiza tu correo dentro de una vista que has creado previamente.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function notificarStockMercancia($usuario,$asunto) {
     // IMPORTANTE: NO implementes aqui el metodo de envio de email 
     // por ejemplo mail(..) o phpmailer(..bla..) eso es
     // responsabilidad del metodo sendEmail que haces mas abajo
     // aqui solo dedicate a preparar el correo !!
     $this->sendEmail(
       $usuario->email,$asunto, 
       $this->render('vista_stockvence',array('data'=>$usuario))
     );
  }
  
  public function sendEmail($to,$subject,$body){
     // usa esto para que el correo se envíe por la via estandar mail()
     // si quieres usar otro método deberás comentar esta linea e implementar 
     // tu propio mecanismo.
     parent::sendEmail($to,$subject,$body);
  }
}

4. Para usar este nuevo método en tu aplicación:

 Yii::app()->crugemailer->notificarStockMercancia($usuarioSeleccionado,"asunto");

¿ Cómo cambiar el método de envío de correo ? (mail, phpmailer etc)

Tienes dos alternativas para cambiar el método de envío de correo, supongamos que no te gusta o no te sirve la función mail() y prefieres usar la librería PHPMailer:


  • Método 1: (preferido)

Editas tu propia clase 'MiClaseCrugeMailer' que has creado en tu propia ruta de 'applications.components' y en ella editas el método sendEmail para implementar la solución.

  • Método 2:

Editas CrugeMailerBase.php y en ella editas el método sendEmail para implementar la solución.

Personalmente recomiendo el "método 1" ya que evita la manipulación del core de CrugeMailer, delegando asi a tu aplicación web la "especialización", además ayuda a que recibas actualizaciones a crugemailer via git sin que haya conflicto. El "método 2" es referenciado en el README pero para el momento de escribirlo no se había tomado en cuenta la actualización via GIT entre otras cosas.

Beneficios obtenidos al usar CrugeMailer

  • Se logra que tú hagas envíos de correos orientados a tu modelo de negocio, sin hacer espaguetti con el código!!, centralizando y encapsulando el código... todos tus correos invocarán a sendEmail...y no tendrás que crear y duplicar o triplicar el esfuerzo de escribir las funciones de correo.
  • Se logra que puedas clarificar el codigo...envias un correo con un proposito muy definido:

Yii::app()->crugemailer->nuevoCorreoAlCliente($usuario, "hola")

  • Se logra que en cualquier momento puedas decidir por cual via se envia el correo, si por mail() o por phpmailer() o por cualquier otra que tu decidas..solo cambiando un metodo..sin hacer "código espaguetti" ("código espaguetti" = código con hiper-dependencias, necio y ofuscado que termina acabando con la vida del proyecto entero en menos de tres meses gracias a la flojera de no organizar ni leer, ahh lo olvidaba, además en algo menos de 6 meses solo dios y tu (si te acuerdas) entenderán que se hizo ahi).

¿Cómo hacer un CRON (BATCH) con CrugeMailer ?

Primero, qué es un "CRON". Es acrónimo para "CRON JOB" o "TAREA PROGRAMADA", que se encarga de despachar correos en forma de LOTES (BATCH).

Segundo, CrugeMailer no va a correr como una tarea programada por si solo porque en PHP no existen las tareas programadas dado que la arquitectura de PHP no admite hilos secundarios como lo haría Java. Por tanto para hacer una tarea programada en PHP se invoca un script (o una funcion) cada cierto tiempo usando un TRIGGER (disparador):

Tipos de Disparador (TRIGGER):

Hay dos metodos para ejecutar una tarea programada en Php:

1) Con un CRONJOB de tu "sistema operativo" mandas a ejecutar un script periódicamente. Pregunta si tu hosting dispone de CronJobs, algunos lo tienen, en la config del hosting se le indica el tiempo y la ruta de un script y sera el servidor quien corra el script cada cierto tiempo. Si es windows, puedes crear una tarea programada que corra un .BAT con una sentencia PHP que ejecute el script, o, simplemente que corra un script por su URL.

2) Pones un contador (en una tabla o en memoria de sesion) en tu aplicación, y cada vez que alguien invoque a tu archivo "index.php" incrementas ese valor, y si se llega al "umbral" ejecutas un método en tu aplicación.

Sea cual sea el método de lanzamiento o "TRIGGER", siempre invocarás un método en una clase, el cual va a ejecutar una lectura de mensajes almacenados y los enviará por correo. Este es el concepto principal.

¿ Cómo hacerlo con CrugeMailer ?

(para saber mas lee "Cómo Personalizar CrugeMailer" mas arriba).


1. Primero, no mandas el email cada vez que ocurre algo en tu negocio (como normlamente sucedería sin un proceso de batch). Es decir, en vez de enviar el email de una vez lo almacenarás.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  public function sendEmail($to,$subject,$body){
     // en vez de enviarlo:
     // parent::sendEmail($to,$subject,$body);

     // lo guardas en un modelo llamado protected/models/Correo.php
     $c = new Correo();
     $c->asunto = $subject;
     $c->body = $body; // es un LONGBLOB en tu modelo
     $c->to = $to;
     $c->enviado = false;
     $c->insert();
  }

}

2. Creas un metodo trigger() para que sea invocado mas adelante por el disparador del cronjob al momento que quieras enviar los correos realmente.

 
<?php 
// esta clase va en: 'protected/components/MiClaseCrugeMailer.php' (no dentro de cruge!!)
class MiClaseCrugeMailer extends CrugeMailerBase {

  // en config/main en el setup de crugemailer le pones:
  // 'limit'=>10,  para indicar cuantos emailas por vez quieres mandar.
  public $limit;

  // metodo usado por tu disparador:
  //   Yii::app()->crugemailer->trigger();
  //
  public function trigger() {
     $lista = Correo::model()->findAllByAttributes(array('enviado'=>false));
     $n=0;
     foreach($lista as $c){
         $this->sendEmailReal($c->to, $c->asunto,$c->body);
         if($n++ > $this->limit)
           break;
     }
  }

  public function sendEmailReal($to,$subject,$body){
     // AQUI PONES TU PHPMAILER...O..
     // simplemente por defecto a:
     // (este es el sendEmail de CrugeMailerBase)
     parent::sendEmail($to,$subject,$body); 
  }

  public function sendEmail($to,$subject,$body){
     // en vez de enviarlo:
     // parent::sendEmail($to,$subject,$body);

     // lo guardas en un modelo llamado protected/models/Correo.php
     $c = new Correo();
     $c->asunto = $subject;
     $c->body = $body; // es un LONGBLOB en tu modelo
     $c->to = $to;
     $c->enviado = false;
     $c->insert();
  }

}


3. Ahora creas un script disparador. (IMPORTANTE: considera protegerlo de ejecución desde IP's extrañas)

por ejemplo asumo el metodo1 de ejecución (usando un script, ver inicio del tema) tu aplicación web tendrá un CronJob de sistema operativo que lanzará este script periodicamente:

 http://tuapp.com/cronjob-email.php
 
<?php
// llamalo:  /tuapp/cronjob-email.php
//
$yii=dirname(__FILE__).'/../../yii/framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';

defined('YII_DEBUG') or define('YII_DEBUG',false);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3);

$_GET['r'] = '/site/disparador';

require_once($yii);
Yii::createWebApplication($config)->run();


4. En siteController.php, creas un action PROTEGIDO por un CAccessControl Rule de YII que prevenga que una IP desconocida lo ejecute: (mira la documentacion yii de CAccessControl)

 
public function actionDisparador(){
   $this->crugemailer->trigger();
}

LISTO. tienes envio de correos en procesos de lotes usando un cron-job.

Layout para los correos

CrugeMailer aplica (y busca) un layout especial para las vistas que tienen que ver con correos, este layout es llamado 'mailer.php':

 /tuapp/protected/views/layouts/mailer.php

cuando CrugeMailer esta operando dentro de Cruge el layout sera:

 /protected/modules/cruge/views/layouts/mailer.php


Probando los Correos

Cuando el flag "debug" es "true" (en la config de 'crugemailer') se copiara a /protected/runtime/application.log el correo generado tal cual como si se le estuviera mandando al cliente:

2012/11/26 15:07:00 [info] [application] email:
to:nadieporahora@xxx.com
subject: Anomalia tipo MARCAJE  pendiente por resolver para el empleado: Christian Salazar
body:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<meta name="language" content="en" />
</head>
<body>
	<h1>Recordatorio de Anomalía presente</h1>
<ul>
<li>Empleado Afectado: <b>Christian Salazar</b></li>
<li>Empresa: <b>Gobierno Bolivariano de Venezuela</b></li>
<li>Tipo Anomalia: <b>MARCAJE</b></li>
<li>Descripcion: <b>anomalia de marcaje </b></li>
<li>Turno: <b>sin turno</b></li>
<li>Fecha/Hora Creacion: <b>2012/11/25</b></li>
<li>#ID: <b>4</b></li>
</ul>
</body>

Herramientas personales